From 590a912cc9a17cebab0b2f8615e32253d78ec09b Mon Sep 17 00:00:00 2001 From: Peter Beams Date: Tue, 3 Mar 2015 01:33:42 +0000 Subject: [PATCH 001/295] 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 fdb9ab3d6..af89fc04f 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 a357e5b1d..9ec79d18c 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 0ab2919fd..3e37a0308 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 000000000..b4d23cd43 --- /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 002/295] 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 b4d23cd43..96437c18d 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 003/295] 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 96437c18d..265ebe8da 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 004/295] 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 000000000..4bf73b975 --- /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 005/295] 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 0ab2919fd..8c9cbd5d3 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 4bf73b975..9e85540ef 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 000000000..25ab24ebc --- /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 006/295] 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 000000000..7f72d6eba --- /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 030192dfd..f73403d6c 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 007/295] 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 9e85540ef..34049e538 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()}, - }) + 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 + } - 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) - } + 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 25ab24ebc..7b2d98157 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 008/295] 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 34049e538..3a9ad8563 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 7b2d98157..33d54d0ae 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 009/295] 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 265ebe8da..3c7aa74a4 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 010/295] 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 3c7aa74a4..4241f8bde 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 000000000..27eedba45 --- /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 000000000..2ba075b08 --- /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 011/295] 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 af89fc04f..fdb9ab3d6 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 18dbe92e2..65abbe23c 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 4241f8bde..acf5b6a17 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 27eedba45..f48a4c4d8 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 012/295] 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 65abbe23c..8bc9adab5 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 2ba075b08..000000000 --- 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 013/295] 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 f48a4c4d8..b6388151a 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 014/295] 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 3e37a0308..4963bfa48 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 1ae50a18f85a14904e0c64a22d041061d3fc01aa Mon Sep 17 00:00:00 2001 From: stungtoat Date: Fri, 6 Mar 2015 22:13:07 -0800 Subject: [PATCH 015/295] add network field to the network_interface --- builtin/providers/google/resource_compute_instance.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/providers/google/resource_compute_instance.go b/builtin/providers/google/resource_compute_instance.go index 3b3e86ded..80defcf4c 100644 --- a/builtin/providers/google/resource_compute_instance.go +++ b/builtin/providers/google/resource_compute_instance.go @@ -564,6 +564,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error networkInterfaces = append(networkInterfaces, map[string]interface{}{ "name": iface.Name, "address": iface.NetworkIP, + "network": iface.Network, "access_config": accessConfigs, }) } From d253fff4e51cf291515d10050c06b3496c630509 Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Tue, 10 Mar 2015 09:49:46 +1000 Subject: [PATCH 016/295] 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 3a9ad8563..4b047af32 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 017/295] 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 4b047af32..2f06a29a7 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 018/295] Format the resource_vpn_gateway*.go files. --- .../providers/aws/resource_aws_vpn_gateway.go | 402 +++++++++--------- .../aws/resource_aws_vpn_gateway_test.go | 16 +- 2 files changed, 208 insertions(+), 210 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpn_gateway.go b/builtin/providers/aws/resource_aws_vpn_gateway.go index 2f06a29a7..e00472576 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 + ec2conn := meta.(*AWSClient).awsEC2conn - createOpts := &ec2.CreateVPNGatewayRequest{ - AvailabilityZone: aws.String(d.Get("availability_zone").(string)), - Type: aws.String("ipsec.1"), - } + 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) - } + // 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) + // 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) + // 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 - } + 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 - } - } + // 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 + ec2conn := meta.(*AWSClient).awsEC2conn - // Detach if it is attached - if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { - return err - } + // Detach if it is attached + if err := resourceAwsVpnGatewayDetach(d, meta); err != nil { + return err + } - log.Printf("[INFO] Deleting VPN gateway: %s", d.Id()) + 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 - } + 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 - } + ec2err, ok := err.(aws.APIError) + if !ok { + return err + } - switch ec2err.Code { - case "InvalidVpnGatewayID.NotFound": - return nil - case "IncorrectState": - return err // retry - } + switch ec2err.Code { + case "InvalidVpnGatewayID.NotFound": + return nil + case "IncorrectState": + return err // retry + } - return resource.RetryError{Err: err} - }) + return resource.RetryError{Err: err} + }) } func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + 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 - } + 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)) + 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 - } + _, 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) - } + 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 + return nil } func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).awsEC2conn + ec2conn := meta.(*AWSClient).awsEC2conn - // Get the old VPC ID to detach from - vpcID, _ := d.GetChange("vpc_id") + // 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 - } + 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)) + 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 - } - } + 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 err != nil { + return err + } + } - if !wait { - return nil - } + 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) - } + // 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 + 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 - } - } + 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 - } + 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 - } + 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() - } + 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 - } - } + 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 - } + 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] + vpnGateway := &resp.VPNGateways[0] - if time.Now().Sub(start) > 10*time.Second { - return vpnGateway, expected, nil - } + 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 - } + if len(vpnGateway.VPCAttachments) == 0 { + // No attachments, we're detached + return vpnGateway, "detached", nil + } - return vpnGateway, *vpnGateway.VPCAttachments[0].State, 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 33d54d0ae..2d3edbe3b 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 019/295] 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 7f72d6eba..b64000ce5 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 f7512ca29fcf2b5ef690214cd9859c738a4c86ba Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Tue, 17 Feb 2015 16:28:33 +0000 Subject: [PATCH 020/295] Initial commit. This adds the initial bits of a Docker provider. Docker's API is huge and only a small subset is currently implemented, but this is expected to grow over time. Currently it's enough to satisfy the use cases of probably 95% of Docker users. I'm preparing this initial pull request as a preview step for feedback. My ideal scenario would be to develop this within a branch in the main repository; the more eyes and testing and pitching in on the code, the better (this would avoid a merge request-to-the-merge-request scenario, as I figure this will be built up over the longer term, even before a merge into master). Unit tests do not exist yet. Right now I've just been focused on getting initial functionality ported over. I've been testing each option extensively via the Docker inspect capabilities. This code (C)2014-2015 Akamai Technologies, Inc. --- CONTRIBUTING.md | 4 +- builtin/bins/provider-docker/main.go | 12 + builtin/bins/provider-docker/main_test.go | 1 + builtin/providers/docker/config.go | 24 ++ builtin/providers/docker/provider.go | 34 +++ .../docker/resource_docker_container.go | 222 ++++++++++++++ .../docker/resource_docker_container_funcs.go | 282 ++++++++++++++++++ .../providers/docker/resource_docker_image.go | 31 ++ .../docker/resource_docker_image_funcs.go | 177 +++++++++++ config.go | 2 +- 10 files changed, 786 insertions(+), 3 deletions(-) create mode 100644 builtin/bins/provider-docker/main.go create mode 100644 builtin/bins/provider-docker/main_test.go create mode 100644 builtin/providers/docker/config.go create mode 100644 builtin/providers/docker/provider.go create mode 100644 builtin/providers/docker/resource_docker_container.go create mode 100644 builtin/providers/docker/resource_docker_container_funcs.go create mode 100644 builtin/providers/docker/resource_docker_image.go create mode 100644 builtin/providers/docker/resource_docker_image_funcs.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87f5ca66d..f5554557f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,8 +53,8 @@ If you have never worked with Go before, you will have to complete the following steps in order to be able to compile and test Terraform (or use the Vagrantfile in this repo to stand up a dev VM). -1. Install Go. Make sure the Go version is at least Go 1.2. Terraform will not work with anything less than - Go 1.2. On a Mac, you can `brew install go` to install Go 1.2. +1. Install Go. Make sure the Go version is at least Go 1.4. Terraform will not work with anything less than + Go 1.4. On a Mac, you can `brew install go` to install Go 1.4. 2. Set and export the `GOPATH` environment variable and update your `PATH`. For example, you can add to your `.bash_profile`. diff --git a/builtin/bins/provider-docker/main.go b/builtin/bins/provider-docker/main.go new file mode 100644 index 000000000..a54af4c02 --- /dev/null +++ b/builtin/bins/provider-docker/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/docker" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: docker.Provider, + }) +} diff --git a/builtin/bins/provider-docker/main_test.go b/builtin/bins/provider-docker/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/builtin/bins/provider-docker/main_test.go @@ -0,0 +1 @@ +package main diff --git a/builtin/providers/docker/config.go b/builtin/providers/docker/config.go new file mode 100644 index 000000000..40355b24f --- /dev/null +++ b/builtin/providers/docker/config.go @@ -0,0 +1,24 @@ +package docker + +import dc "github.com/fsouza/go-dockerclient" + +type Config struct { + DockerHost string + SkipPull bool +} + +type Data struct { + DockerImages map[string]*dc.APIImages +} + +// NewClient() returns a new Docker client. +func (c *Config) NewClient() (*dc.Client, error) { + return dc.NewClient(c.DockerHost) +} + +// NewData() returns a new data struct. +func (c *Config) NewData() *Data { + return &Data{ + DockerImages: map[string]*dc.APIImages{}, + } +} diff --git a/builtin/providers/docker/provider.go b/builtin/providers/docker/provider.go new file mode 100644 index 000000000..d01ec385c --- /dev/null +++ b/builtin/providers/docker/provider.go @@ -0,0 +1,34 @@ +package docker + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "docker_host": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("DOCKER_HOST", "unix:/run/docker.sock"), + Description: "The Docker daemon endpoint", + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "docker_container": resourceDockerContainer(), + "docker_image": resourceDockerImage(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + config := Config{ + DockerHost: d.Get("docker_host").(string), + } + + return &config, nil +} diff --git a/builtin/providers/docker/resource_docker_container.go b/builtin/providers/docker/resource_docker_container.go new file mode 100644 index 000000000..50b501ca2 --- /dev/null +++ b/builtin/providers/docker/resource_docker_container.go @@ -0,0 +1,222 @@ +package docker + +import ( + "bytes" + "fmt" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDockerContainer() *schema.Resource { + return &schema.Resource{ + Create: resourceDockerContainerCreate, + Read: resourceDockerContainerRead, + Update: resourceDockerContainerUpdate, + Delete: resourceDockerContainerDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + // Indicates whether the container must be running. + // + // An assumption is made that configured containers + // should be running; if not, they should not be in + // the configuration. Therefore a stopped container + // should be started. Set to false to have the + // provider leave the container alone. + // + // Actively-debugged containers are likely to be + // stopped and started manually, and Docker has + // some provisions for restarting containers that + // stop. The utility here comes from the fact that + // this will delete and re-create the container + // following the principle that the containers + // should be pristine when started. + "must_run": &schema.Schema{ + Type: schema.TypeBool, + Default: true, + Optional: true, + }, + + // ForceNew is not true for image because we need to + // sane this against Docker image IDs, as each image + // can have multiple names/tags attached do it. + "image": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "hostname": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "domainname": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "command": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "dns": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: stringSetHash, + }, + + "publish_all_ports": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "volumes": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: getVolumesElem(), + Set: resourceDockerVolumesHash, + }, + + "ports": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: getPortsElem(), + Set: resourceDockerPortsHash, + }, + + "env": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: stringSetHash, + }, + }, + } +} + +func getVolumesElem() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "from_container": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "container_path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "host_path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "read_only": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func getPortsElem() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "internal": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "external": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + + "ip": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Default: "tcp", + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceDockerPortsHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + buf.WriteString(fmt.Sprintf("%v-", m["internal"].(int))) + + if v, ok := m["external"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(int))) + } + + if v, ok := m["ip"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(string))) + } + + if v, ok := m["protocol"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(string))) + } + + return hashcode.String(buf.String()) +} + +func resourceDockerVolumesHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + if v, ok := m["from_container"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(string))) + } + + if v, ok := m["container_path"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(string))) + } + + if v, ok := m["host_path"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(string))) + } + + if v, ok := m["read_only"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(bool))) + } + + return hashcode.String(buf.String()) +} + +func stringSetHash(v interface{}) int { + return hashcode.String(v.(string)) +} diff --git a/builtin/providers/docker/resource_docker_container_funcs.go b/builtin/providers/docker/resource_docker_container_funcs.go new file mode 100644 index 000000000..d0daa08bb --- /dev/null +++ b/builtin/providers/docker/resource_docker_container_funcs.go @@ -0,0 +1,282 @@ +package docker + +import ( + "errors" + "fmt" + "strconv" + "strings" + + dc "github.com/fsouza/go-dockerclient" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + client, err := config.NewClient() + if err != nil { + return fmt.Errorf("Unable to connect to Docker: %s", err) + } + + data := config.NewData() + + if err := fetchLocalImages(data, client); err != nil { + return err + } + + image := d.Get("image").(string) + if _, ok := data.DockerImages[image]; !ok { + if _, ok := data.DockerImages[image+":latest"]; !ok { + return fmt.Errorf("Unable to find image %s", image) + } else { + image = image + ":latest" + } + } + + // The awesome, wonderful, splendiferous, sensical + // Docker API now lets you specify a HostConfig in + // CreateContainerOptions, but in my testing it still only + // actually applies HostConfig options set in StartContainer. + // How cool is that? + createOpts := dc.CreateContainerOptions{ + Name: d.Get("name").(string), + Config: &dc.Config{ + Image: image, + Hostname: d.Get("hostname").(string), + Domainname: d.Get("domainname").(string), + }, + } + + if v, ok := d.GetOk("env"); ok { + createOpts.Config.Env = stringSetToStringSlice(v.(*schema.Set)) + } + + if v, ok := d.GetOk("command"); ok { + createOpts.Config.Cmd = stringListToStringSlice(v.([]interface{})) + } + + exposedPorts := map[dc.Port]struct{}{} + portBindings := map[dc.Port][]dc.PortBinding{} + + if v, ok := d.GetOk("ports"); ok { + exposedPorts, portBindings = portSetToDockerPorts(v.(*schema.Set)) + } + if len(exposedPorts) != 0 { + createOpts.Config.ExposedPorts = exposedPorts + } + + volumes := map[string]struct{}{} + binds := []string{} + volumesFrom := []string{} + + if v, ok := d.GetOk("volumes"); ok { + volumes, binds, volumesFrom, err = volumeSetToDockerVolumes(v.(*schema.Set)) + if err != nil { + return fmt.Errorf("Unable to parse volumes: %s", err) + } + } + if len(volumes) != 0 { + createOpts.Config.Volumes = volumes + } + + var retContainer *dc.Container + if retContainer, err = client.CreateContainer(createOpts); err != nil { + return fmt.Errorf("Unable to create container: %s", err) + } + if retContainer == nil { + return fmt.Errorf("Returned container is nil") + } + + d.SetId(retContainer.ID) + + hostConfig := &dc.HostConfig{ + PublishAllPorts: d.Get("publish_all_ports").(bool), + } + + if len(portBindings) != 0 { + hostConfig.PortBindings = portBindings + } + + if len(binds) != 0 { + hostConfig.Binds = binds + } + if len(volumesFrom) != 0 { + hostConfig.VolumesFrom = volumesFrom + } + + if v, ok := d.GetOk("dns"); ok { + hostConfig.DNS = stringSetToStringSlice(v.(*schema.Set)) + } + + if err := client.StartContainer(retContainer.ID, hostConfig); err != nil { + return fmt.Errorf("Unable to start container: %s", err) + } + + return resourceDockerContainerRead(d, meta) +} + +func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + client, err := config.NewClient() + if err != nil { + return fmt.Errorf("Unable to connect to Docker: %s", err) + } + + apiContainer, err := fetchDockerContainer(d.Get("name").(string), client) + if err != nil { + return err + } + + if apiContainer == nil { + // This container doesn't exist anymore + d.SetId("") + + return nil + } + + container, err := client.InspectContainer(apiContainer.ID) + if err != nil { + return fmt.Errorf("Error inspecting container %s: %s", apiContainer.ID, err) + } + + if d.Get("must_run").(bool) && !container.State.Running { + return resourceDockerContainerDelete(d, meta) + } + + return nil +} + +func resourceDockerContainerUpdate(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + client, err := config.NewClient() + if err != nil { + return fmt.Errorf("Unable to connect to Docker: %s", err) + } + + removeOpts := dc.RemoveContainerOptions{ + ID: d.Id(), + RemoveVolumes: true, + Force: true, + } + + if err := client.RemoveContainer(removeOpts); err != nil { + return fmt.Errorf("Error deleting container %s: %s", d.Id(), err) + } + + d.SetId("") + return nil +} + +func stringListToStringSlice(stringList []interface{}) []string { + ret := []string{} + for _, v := range stringList { + ret = append(ret, v.(string)) + } + return ret +} + +func stringSetToStringSlice(stringSet *schema.Set) []string { + ret := []string{} + if stringSet == nil { + return ret + } + for _, envVal := range stringSet.List() { + ret = append(ret, envVal.(string)) + } + return ret +} + +func fetchDockerContainer(name string, client *dc.Client) (*dc.APIContainers, error) { + apiContainers, err := client.ListContainers(dc.ListContainersOptions{All: true}) + + if err != nil { + return nil, fmt.Errorf("Error fetching container information from Docker: %s\n", err) + } + + for _, apiContainer := range apiContainers { + // Sometimes the Docker API prefixes container names with / + // like it does in these commands. But if there's no + // set name, it just uses the ID without a /...ugh. + var dockerContainerName string + if len(apiContainer.Names) > 0 { + dockerContainerName = strings.TrimLeft(apiContainer.Names[0], "/") + } else { + dockerContainerName = apiContainer.ID + } + + if dockerContainerName == name { + return &apiContainer, nil + } + } + + return nil, nil +} + +func portSetToDockerPorts(ports *schema.Set) (map[dc.Port]struct{}, map[dc.Port][]dc.PortBinding) { + retExposedPorts := map[dc.Port]struct{}{} + retPortBindings := map[dc.Port][]dc.PortBinding{} + + for _, portInt := range ports.List() { + port := portInt.(map[string]interface{}) + internal := port["internal"].(int) + protocol := port["protocol"].(string) + + exposedPort := dc.Port(strconv.Itoa(internal) + "/" + protocol) + retExposedPorts[exposedPort] = struct{}{} + + external, extOk := port["external"].(int) + ip, ipOk := port["ip"].(string) + + if extOk { + portBinding := dc.PortBinding{ + HostPort: strconv.Itoa(external), + } + if ipOk { + portBinding.HostIP = ip + } + retPortBindings[exposedPort] = append(retPortBindings[exposedPort], portBinding) + } + } + + return retExposedPorts, retPortBindings +} + +func volumeSetToDockerVolumes(volumes *schema.Set) (map[string]struct{}, []string, []string, error) { + retVolumeMap := map[string]struct{}{} + retHostConfigBinds := []string{} + retVolumeFromContainers := []string{} + + for _, volumeInt := range volumes.List() { + volume := volumeInt.(map[string]interface{}) + fromContainer := volume["from_container"].(string) + containerPath := volume["container_path"].(string) + hostPath := volume["host_path"].(string) + readOnly := volume["read_only"].(bool) + + switch { + case len(fromContainer) == 0 && len(containerPath) == 0: + return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Volume entry without container path or source container") + case len(fromContainer) != 0 && len(containerPath) != 0: + return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, errors.New("Both a container and a path specified in a volume entry") + case len(fromContainer) != 0: + retVolumeFromContainers = append(retVolumeFromContainers, fromContainer) + case len(hostPath) != 0: + readWrite := "rw" + if readOnly { + readWrite = "ro" + } + retVolumeMap[containerPath] = struct{}{} + retHostConfigBinds = append(retHostConfigBinds, hostPath+":"+containerPath+":"+readWrite) + default: + retVolumeMap[containerPath] = struct{}{} + } + } + + return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, nil +} diff --git a/builtin/providers/docker/resource_docker_image.go b/builtin/providers/docker/resource_docker_image.go new file mode 100644 index 000000000..54822d738 --- /dev/null +++ b/builtin/providers/docker/resource_docker_image.go @@ -0,0 +1,31 @@ +package docker + +import ( + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDockerImage() *schema.Resource { + return &schema.Resource{ + Create: resourceDockerImageCreate, + Read: resourceDockerImageRead, + Update: resourceDockerImageUpdate, + Delete: resourceDockerImageDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "keep_updated": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + + "latest": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} diff --git a/builtin/providers/docker/resource_docker_image_funcs.go b/builtin/providers/docker/resource_docker_image_funcs.go new file mode 100644 index 000000000..a34f21994 --- /dev/null +++ b/builtin/providers/docker/resource_docker_image_funcs.go @@ -0,0 +1,177 @@ +package docker + +import ( + "fmt" + "strings" + + dc "github.com/fsouza/go-dockerclient" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDockerImageCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + apiImage, err := findImage(d, config) + if err != nil { + return fmt.Errorf("Unable to read Docker image into resource: %s", err) + } + + d.SetId(apiImage.ID + d.Get("name").(string)) + d.Set("latest", apiImage.ID) + + return nil +} + +func resourceDockerImageRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + apiImage, err := findImage(d, config) + if err != nil { + return fmt.Errorf("Unable to read Docker image into resource: %s", err) + } + + d.Set("latest", apiImage.ID) + + return nil +} + +func resourceDockerImageUpdate(d *schema.ResourceData, meta interface{}) error { + // We need to re-read in case switching parameters affects + // the value of "latest" or others + + return resourceDockerImageRead(d, meta) +} + +func resourceDockerImageDelete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} + +func fetchLocalImages(data *Data, client *dc.Client) error { + images, err := client.ListImages(dc.ListImagesOptions{All: false}) + if err != nil { + return fmt.Errorf("Unable to list Docker images: %s", err) + } + + // Docker uses different nomenclatures in different places...sometimes a short + // ID, sometimes long, etc. So we store both in the map so we can always find + // the same image object. We store the tags, too. + for i, image := range images { + data.DockerImages[image.ID[:12]] = &images[i] + data.DockerImages[image.ID] = &images[i] + for _, repotag := range image.RepoTags { + data.DockerImages[repotag] = &images[i] + } + } + + return nil +} + +func pullImage(data *Data, client *dc.Client, image string) error { + // TODO: Test local registry handling. It should be working + // based on the code that was ported over + + pullOpts := dc.PullImageOptions{} + + splitImageName := strings.Split(image, ":") + switch { + + // It's in registry:port/repo:tag format + case len(splitImageName) == 3: + splitPortRepo := strings.Split(splitImageName[1], "/") + pullOpts.Registry = splitImageName[0] + ":" + splitPortRepo[0] + pullOpts.Repository = splitPortRepo[1] + pullOpts.Tag = splitImageName[2] + + // It's either registry:port/repo or repo:tag with default registry + case len(splitImageName) == 2: + splitPortRepo := strings.Split(splitImageName[1], "/") + switch len(splitPortRepo) { + + // registry:port/repo + case 2: + pullOpts.Registry = splitImageName[0] + ":" + splitPortRepo[0] + pullOpts.Repository = splitPortRepo[1] + pullOpts.Tag = "latest" + + // repo:tag + case 1: + pullOpts.Repository = splitImageName[0] + pullOpts.Tag = splitImageName[1] + } + + default: + pullOpts.Repository = image + } + + if err := client.PullImage(pullOpts, dc.AuthConfiguration{}); err != nil { + return fmt.Errorf("Error pulling image %s: %s\n", image, err) + } + + return fetchLocalImages(data, client) +} + +func getImageTag(image string) string { + splitImageName := strings.Split(image, ":") + switch { + + // It's in registry:port/repo:tag format + case len(splitImageName) == 3: + return splitImageName[2] + + // It's either registry:port/repo or repo:tag with default registry + case len(splitImageName) == 2: + splitPortRepo := strings.Split(splitImageName[1], "/") + if len(splitPortRepo) == 2 { + return "" + } else { + return splitImageName[1] + } + } + + return "" +} + +func findImage(d *schema.ResourceData, config *Config) (*dc.APIImages, error) { + client, err := config.NewClient() + if err != nil { + return nil, fmt.Errorf("Unable to connect to Docker: %s", err) + } + + data := config.NewData() + + if err := fetchLocalImages(data, client); err != nil { + return nil, err + } + + imageName := d.Get("name").(string) + if imageName == "" { + return nil, fmt.Errorf("Empty image name is not allowed") + } + + searchLocal := func() *dc.APIImages { + if apiImage, ok := data.DockerImages[imageName]; ok { + return apiImage + } + if apiImage, ok := data.DockerImages[imageName+":latest"]; ok { + imageName = imageName + ":latest" + return apiImage + } + return nil + } + + foundImage := searchLocal() + + if d.Get("keep_updated").(bool) || foundImage == nil { + if err := pullImage(data, client, imageName); err != nil { + return nil, fmt.Errorf("Unable to pull image %s: %s", imageName, err) + } + } + + foundImage = searchLocal() + if foundImage != nil { + return foundImage, nil + } + + return nil, fmt.Errorf("Unable to find or pull image %s", imageName) +} diff --git a/config.go b/config.go index 583d7ddb2..648223888 100644 --- a/config.go +++ b/config.go @@ -179,7 +179,7 @@ func (c *Config) discoverSingle(glob string, m *map[string]string) error { continue } - log.Printf("[DEBUG] Discoverd plugin: %s = %s", parts[2], match) + log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match) (*m)[parts[2]] = match } From cfd8d913bdffde7ea7536e53f474303227d3498e Mon Sep 17 00:00:00 2001 From: Dan Everton Date: Thu, 12 Mar 2015 08:13:39 +1000 Subject: [PATCH 021/295] 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 e00472576..d6ffcef97 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 022/295] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8470f90b6..c9134c82b 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 023/295] 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 08325c783..f3e4df755 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 024/295] 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 fcd781c61..323a67486 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 025/295] 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 acf5b6a17..282ba9160 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 b6388151a..1478c289e 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 026/295] 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 282ba9160..f82bdc69b 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 617c2bbf9..2855506df 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 b85adc51a..84a7ad09d 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 027/295] 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 f82bdc69b..c4829f4b8 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 1478c289e..413533e56 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 2855506df..09d8a10be 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 84a7ad09d..bc5cf68e4 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 7034619863d14d4c43996e657467868fa8ddee07 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 17 Mar 2015 15:48:10 -0500 Subject: [PATCH 028/295] 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 c8051813f..8a307babb 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 f74e68ea46ace04a89174bee6eb448c66aad2463 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 17 Mar 2015 17:22:30 -0500 Subject: [PATCH 029/295] 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 a5eda1a64..68c5b52e6 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 030/295] 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 a1bd196d1..fe1494ff8 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 000000000..e69de29bb From 33496eeaf86e73b9042ffa7ee2ee018e39178e25 Mon Sep 17 00:00:00 2001 From: David Watson Date: Wed, 18 Mar 2015 17:10:39 +0000 Subject: [PATCH 031/295] Update Google API import to point to the new location. --- builtin/providers/google/config.go | 3 +-- builtin/providers/google/disk_type.go | 2 +- builtin/providers/google/operation.go | 3 ++- builtin/providers/google/resource_compute_address.go | 4 ++-- builtin/providers/google/resource_compute_address_test.go | 2 +- builtin/providers/google/resource_compute_disk.go | 4 ++-- builtin/providers/google/resource_compute_disk_test.go | 2 +- builtin/providers/google/resource_compute_firewall.go | 4 ++-- builtin/providers/google/resource_compute_firewall_test.go | 2 +- builtin/providers/google/resource_compute_forwarding_rule.go | 4 ++-- .../providers/google/resource_compute_http_health_check.go | 4 ++-- builtin/providers/google/resource_compute_instance.go | 4 ++-- .../providers/google/resource_compute_instance_template.go | 4 ++-- .../google/resource_compute_instance_template_test.go | 2 +- builtin/providers/google/resource_compute_instance_test.go | 2 +- builtin/providers/google/resource_compute_network.go | 4 ++-- builtin/providers/google/resource_compute_network_test.go | 2 +- builtin/providers/google/resource_compute_route.go | 4 ++-- builtin/providers/google/resource_compute_route_test.go | 2 +- builtin/providers/google/resource_compute_target_pool.go | 4 ++-- 20 files changed, 31 insertions(+), 31 deletions(-) diff --git a/builtin/providers/google/config.go b/builtin/providers/google/config.go index 9ae889482..254cb3ebf 100644 --- a/builtin/providers/google/config.go +++ b/builtin/providers/google/config.go @@ -7,11 +7,10 @@ import ( "net/http" "os" - "code.google.com/p/google-api-go-client/compute/v1" - "golang.org/x/oauth2" "golang.org/x/oauth2/google" "golang.org/x/oauth2/jwt" + "google.golang.org/api/compute/v1" ) // Config is the configuration structure used to instantiate the Google diff --git a/builtin/providers/google/disk_type.go b/builtin/providers/google/disk_type.go index dfea866db..1653337be 100644 --- a/builtin/providers/google/disk_type.go +++ b/builtin/providers/google/disk_type.go @@ -1,7 +1,7 @@ package google import ( - "code.google.com/p/google-api-go-client/compute/v1" + "google.golang.org/api/compute/v1" ) // readDiskType finds the disk type with the given name. diff --git a/builtin/providers/google/operation.go b/builtin/providers/google/operation.go index 32bf79a5e..b1f2f255b 100644 --- a/builtin/providers/google/operation.go +++ b/builtin/providers/google/operation.go @@ -4,7 +4,8 @@ import ( "bytes" "fmt" - "code.google.com/p/google-api-go-client/compute/v1" + "google.golang.org/api/compute/v1" + "github.com/hashicorp/terraform/helper/resource" ) diff --git a/builtin/providers/google/resource_compute_address.go b/builtin/providers/google/resource_compute_address.go index d67ceb190..9bb9547fe 100644 --- a/builtin/providers/google/resource_compute_address.go +++ b/builtin/providers/google/resource_compute_address.go @@ -5,9 +5,9 @@ import ( "log" "time" - "code.google.com/p/google-api-go-client/compute/v1" - "code.google.com/p/google-api-go-client/googleapi" "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" ) func resourceComputeAddress() *schema.Resource { diff --git a/builtin/providers/google/resource_compute_address_test.go b/builtin/providers/google/resource_compute_address_test.go index ba87169d6..90988bb2c 100644 --- a/builtin/providers/google/resource_compute_address_test.go +++ b/builtin/providers/google/resource_compute_address_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "code.google.com/p/google-api-go-client/compute/v1" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" ) func TestAccComputeAddress_basic(t *testing.T) { diff --git a/builtin/providers/google/resource_compute_disk.go b/builtin/providers/google/resource_compute_disk.go index 72457b9ac..56b7ed25f 100644 --- a/builtin/providers/google/resource_compute_disk.go +++ b/builtin/providers/google/resource_compute_disk.go @@ -5,9 +5,9 @@ import ( "log" "time" - "code.google.com/p/google-api-go-client/compute/v1" - "code.google.com/p/google-api-go-client/googleapi" "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" ) func resourceComputeDisk() *schema.Resource { diff --git a/builtin/providers/google/resource_compute_disk_test.go b/builtin/providers/google/resource_compute_disk_test.go index f99d9ed62..659affff8 100644 --- a/builtin/providers/google/resource_compute_disk_test.go +++ b/builtin/providers/google/resource_compute_disk_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "code.google.com/p/google-api-go-client/compute/v1" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" ) func TestAccComputeDisk_basic(t *testing.T) { diff --git a/builtin/providers/google/resource_compute_firewall.go b/builtin/providers/google/resource_compute_firewall.go index 09d9ca250..2a2433a87 100644 --- a/builtin/providers/google/resource_compute_firewall.go +++ b/builtin/providers/google/resource_compute_firewall.go @@ -6,10 +6,10 @@ import ( "sort" "time" - "code.google.com/p/google-api-go-client/compute/v1" - "code.google.com/p/google-api-go-client/googleapi" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" ) func resourceComputeFirewall() *schema.Resource { diff --git a/builtin/providers/google/resource_compute_firewall_test.go b/builtin/providers/google/resource_compute_firewall_test.go index 9bb92af20..a4a489fba 100644 --- a/builtin/providers/google/resource_compute_firewall_test.go +++ b/builtin/providers/google/resource_compute_firewall_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "code.google.com/p/google-api-go-client/compute/v1" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" ) func TestAccComputeFirewall_basic(t *testing.T) { diff --git a/builtin/providers/google/resource_compute_forwarding_rule.go b/builtin/providers/google/resource_compute_forwarding_rule.go index e87374344..8138ead83 100644 --- a/builtin/providers/google/resource_compute_forwarding_rule.go +++ b/builtin/providers/google/resource_compute_forwarding_rule.go @@ -5,9 +5,9 @@ import ( "log" "time" - "code.google.com/p/google-api-go-client/compute/v1" - "code.google.com/p/google-api-go-client/googleapi" "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" ) func resourceComputeForwardingRule() *schema.Resource { diff --git a/builtin/providers/google/resource_compute_http_health_check.go b/builtin/providers/google/resource_compute_http_health_check.go index 68a4c1348..7f059b860 100644 --- a/builtin/providers/google/resource_compute_http_health_check.go +++ b/builtin/providers/google/resource_compute_http_health_check.go @@ -5,9 +5,9 @@ import ( "log" "time" - "code.google.com/p/google-api-go-client/compute/v1" - "code.google.com/p/google-api-go-client/googleapi" "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" ) func resourceComputeHttpHealthCheck() *schema.Resource { diff --git a/builtin/providers/google/resource_compute_instance.go b/builtin/providers/google/resource_compute_instance.go index 3b3e86ded..803bb277b 100644 --- a/builtin/providers/google/resource_compute_instance.go +++ b/builtin/providers/google/resource_compute_instance.go @@ -5,10 +5,10 @@ import ( "log" "time" - "code.google.com/p/google-api-go-client/compute/v1" - "code.google.com/p/google-api-go-client/googleapi" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" ) func resourceComputeInstance() *schema.Resource { diff --git a/builtin/providers/google/resource_compute_instance_template.go b/builtin/providers/google/resource_compute_instance_template.go index 074e45695..249b98187 100644 --- a/builtin/providers/google/resource_compute_instance_template.go +++ b/builtin/providers/google/resource_compute_instance_template.go @@ -4,10 +4,10 @@ import ( "fmt" "time" - "code.google.com/p/google-api-go-client/compute/v1" - "code.google.com/p/google-api-go-client/googleapi" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" ) func resourceComputeInstanceTemplate() *schema.Resource { diff --git a/builtin/providers/google/resource_compute_instance_template_test.go b/builtin/providers/google/resource_compute_instance_template_test.go index 74133089d..d3e696ec9 100644 --- a/builtin/providers/google/resource_compute_instance_template_test.go +++ b/builtin/providers/google/resource_compute_instance_template_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "code.google.com/p/google-api-go-client/compute/v1" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" ) func TestAccComputeInstanceTemplate_basic(t *testing.T) { diff --git a/builtin/providers/google/resource_compute_instance_test.go b/builtin/providers/google/resource_compute_instance_test.go index 9d16db521..9c53fae04 100644 --- a/builtin/providers/google/resource_compute_instance_test.go +++ b/builtin/providers/google/resource_compute_instance_test.go @@ -5,9 +5,9 @@ import ( "strings" "testing" - "code.google.com/p/google-api-go-client/compute/v1" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" ) func TestAccComputeInstance_basic_deprecated_network(t *testing.T) { diff --git a/builtin/providers/google/resource_compute_network.go b/builtin/providers/google/resource_compute_network.go index 4254da721..5e581eff2 100644 --- a/builtin/providers/google/resource_compute_network.go +++ b/builtin/providers/google/resource_compute_network.go @@ -5,9 +5,9 @@ import ( "log" "time" - "code.google.com/p/google-api-go-client/compute/v1" - "code.google.com/p/google-api-go-client/googleapi" "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" ) func resourceComputeNetwork() *schema.Resource { diff --git a/builtin/providers/google/resource_compute_network_test.go b/builtin/providers/google/resource_compute_network_test.go index ea25b0ff4..89827f576 100644 --- a/builtin/providers/google/resource_compute_network_test.go +++ b/builtin/providers/google/resource_compute_network_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "code.google.com/p/google-api-go-client/compute/v1" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" ) func TestAccComputeNetwork_basic(t *testing.T) { diff --git a/builtin/providers/google/resource_compute_route.go b/builtin/providers/google/resource_compute_route.go index 02aa72652..aec9e8d3d 100644 --- a/builtin/providers/google/resource_compute_route.go +++ b/builtin/providers/google/resource_compute_route.go @@ -5,10 +5,10 @@ import ( "log" "time" - "code.google.com/p/google-api-go-client/compute/v1" - "code.google.com/p/google-api-go-client/googleapi" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" ) func resourceComputeRoute() *schema.Resource { diff --git a/builtin/providers/google/resource_compute_route_test.go b/builtin/providers/google/resource_compute_route_test.go index 065842f85..e4b8627e9 100644 --- a/builtin/providers/google/resource_compute_route_test.go +++ b/builtin/providers/google/resource_compute_route_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "code.google.com/p/google-api-go-client/compute/v1" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "google.golang.org/api/compute/v1" ) func TestAccComputeRoute_basic(t *testing.T) { diff --git a/builtin/providers/google/resource_compute_target_pool.go b/builtin/providers/google/resource_compute_target_pool.go index 98935b84c..83611e2bd 100644 --- a/builtin/providers/google/resource_compute_target_pool.go +++ b/builtin/providers/google/resource_compute_target_pool.go @@ -6,9 +6,9 @@ import ( "strings" "time" - "code.google.com/p/google-api-go-client/compute/v1" - "code.google.com/p/google-api-go-client/googleapi" "github.com/hashicorp/terraform/helper/schema" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" ) func resourceComputeTargetPool() *schema.Resource { From c675c20a48cea55b3995e659dd610ba6a8bfcfe2 Mon Sep 17 00:00:00 2001 From: David Watson Date: Wed, 18 Mar 2015 17:42:03 +0000 Subject: [PATCH 032/295] Update GCE Instance Template tests now that existing disk must exist prior to template creation. --- .../resource_compute_instance_template_test.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/builtin/providers/google/resource_compute_instance_template_test.go b/builtin/providers/google/resource_compute_instance_template_test.go index 74133089d..a3613d614 100644 --- a/builtin/providers/google/resource_compute_instance_template_test.go +++ b/builtin/providers/google/resource_compute_instance_template_test.go @@ -65,7 +65,7 @@ func TestAccComputeInstanceTemplate_disks(t *testing.T) { testAccCheckComputeInstanceTemplateExists( "google_compute_instance_template.foobar", &instanceTemplate), testAccCheckComputeInstanceTemplateDisk(&instanceTemplate, "debian-7-wheezy-v20140814", true, true), - testAccCheckComputeInstanceTemplateDisk(&instanceTemplate, "foo_existing_disk", false, false), + testAccCheckComputeInstanceTemplateDisk(&instanceTemplate, "terraform-test-foobar", false, false), ), }, }, @@ -252,6 +252,14 @@ resource "google_compute_instance_template" "foobar" { }` const testAccComputeInstanceTemplate_disks = ` +resource "google_compute_disk" "foobar" { + name = "terraform-test-foobar" + image = "debian-7-wheezy-v20140814" + size = 10 + type = "pd-ssd" + zone = "us-central1-a" +} + resource "google_compute_instance_template" "foobar" { name = "terraform-test" machine_type = "n1-standard-1" @@ -263,7 +271,7 @@ resource "google_compute_instance_template" "foobar" { } disk { - source = "foo_existing_disk" + source = "terraform-test-foobar" auto_delete = false boot = false } From 988da2f90b4b1e7cf1ea211d4cdd9afb624209f7 Mon Sep 17 00:00:00 2001 From: David Watson Date: Wed, 18 Mar 2015 17:50:03 +0000 Subject: [PATCH 033/295] Updates to GCE Instances and Instance Templates to allow for false values to be set for the auto_delete setting. --- builtin/providers/google/resource_compute_instance.go | 7 ++----- .../providers/google/resource_compute_instance_template.go | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/builtin/providers/google/resource_compute_instance.go b/builtin/providers/google/resource_compute_instance.go index 3b3e86ded..628dfec64 100644 --- a/builtin/providers/google/resource_compute_instance.go +++ b/builtin/providers/google/resource_compute_instance.go @@ -72,6 +72,7 @@ func resourceComputeInstance() *schema.Resource { "auto_delete": &schema.Schema{ Type: schema.TypeBool, Optional: true, + Default: true, ForceNew: true, }, }, @@ -283,11 +284,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err disk.Type = "PERSISTENT" disk.Mode = "READ_WRITE" disk.Boot = i == 0 - disk.AutoDelete = true - - if v, ok := d.GetOk(prefix + ".auto_delete"); ok { - disk.AutoDelete = v.(bool) - } + disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool) // Load up the disk for this disk if specified if v, ok := d.GetOk(prefix + ".disk"); ok { diff --git a/builtin/providers/google/resource_compute_instance_template.go b/builtin/providers/google/resource_compute_instance_template.go index 074e45695..fef3f0859 100644 --- a/builtin/providers/google/resource_compute_instance_template.go +++ b/builtin/providers/google/resource_compute_instance_template.go @@ -58,6 +58,7 @@ func resourceComputeInstanceTemplate() *schema.Resource { "auto_delete": &schema.Schema{ Type: schema.TypeBool, Optional: true, + Default: true, ForceNew: true, }, @@ -235,11 +236,7 @@ func buildDisks(d *schema.ResourceData, meta interface{}) []*compute.AttachedDis disk.Mode = "READ_WRITE" disk.Interface = "SCSI" disk.Boot = i == 0 - disk.AutoDelete = true - - if v, ok := d.GetOk(prefix + ".auto_delete"); ok { - disk.AutoDelete = v.(bool) - } + disk.AutoDelete = d.Get(prefix + ".auto_delete").(bool) if v, ok := d.GetOk(prefix + ".boot"); ok { disk.Boot = v.(bool) From 1979d9b792c34bd9a49be77782c1a245bcfec536 Mon Sep 17 00:00:00 2001 From: "Michael H. Oshita" Date: Thu, 19 Mar 2015 03:45:32 +0900 Subject: [PATCH 034/295] 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 869f4bdc5..f23bbcf16 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 035/295] 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 74c7b5845..5fb4f525a 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 036/295] 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 5fb4f525a..4e966e3b6 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 037/295] 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 a19912eed..797d021ab 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 b1c42721f..e406e55b9 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 3dbad7ae6..42e9023ba 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 038/295] 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 d6ffcef97..b6ecba581 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 2d3edbe3b..21ccb980c 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 039/295] update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8470f90b6..79ca1d5f2 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 040/295] 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 fe1494ff8..1848f314d 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 041/295] 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 | 374 ++++++++++++------ .../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, 587 insertions(+), 154 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 74c7b5845..9ef546764 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,53 +130,28 @@ 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{ - "device_name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "virtual_name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - - "snapshot_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "volume_type": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - }, - - "volume_size": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, - }, - "delete_on_termination": &schema.Schema{ Type: schema.TypeBool, Optional: true, @@ -181,6 +159,12 @@ func resourceAwsInstance() *schema.Resource { ForceNew: true, }, + "device_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "encrypted": &schema.Schema{ Type: schema.TypeBool, Optional: true, @@ -194,17 +178,79 @@ func resourceAwsInstance() *schema.Resource { Computed: true, ForceNew: true, }, + + "snapshot_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "volume_size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "volume_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, }, }, - Set: resourceAwsInstanceBlockDevicesHash, + 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()) + }, + }, + + "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, + }, + + "virtual_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + 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,6 +272,13 @@ func resourceAwsInstance() *schema.Resource { Default: "/dev/sda1", }, + "iops": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + "volume_size": &schema.Schema{ Type: schema.TypeInt, Optional: true, @@ -239,15 +292,19 @@ func resourceAwsInstance() *schema.Resource { Computed: true, ForceNew: true, }, - - "iops": &schema.Schema{ - Type: schema.TypeInt, - 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 := d.Get("block_device"); v != nil { - blockDevices = append(blockDevices, v.(*schema.Set).List()...) - } - - 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.") - } - blockDevices = append(blockDevices, rootBlockDevices...) - } - - if len(blockDevices) > 0 { - runOpts.BlockDeviceMappings = make([]ec2.BlockDeviceMapping, len(blockDevices)) - for i, v := range blockDevices { + if v, ok := d.GetOk("ebs_block_device"); ok { + vL := v.(*schema.Set).List() + 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["snapshot_id"].(string); ok && v != "" { - runOpts.BlockDeviceMappings[i].EBS.SnapshotID = aws.String(v) + 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_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 { - 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 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)), + }) + } + // if err := d.Set("ephemeral_block_device", vL); err != nil { + // return err + // } + } + + 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{}) + ebs := &ec2.EBSBlockDevice{ + DeleteOnTermination: aws.Boolean(bd["delete_on_termination"].(bool)), + } + + 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 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 000000000..adb7a01ec --- /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 000000000..5738f2508 --- /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 941dd5dd4..9e847c43b 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 94f042af3..545dff44b 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 042/295] 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 b19c90304..40e6d64b0 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 043/295] 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 40e6d64b0..20d0701e5 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 044/295] 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 79ca1d5f2..9769581af 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 c4829f4b8..242e0d80c 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 413533e56..5c65cfa0c 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 045/295] 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 9e847c43b..e4ba29807 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 046/295] 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 40e6d64b0..27edd3256 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 e4ba29807..638fceb37 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 047/295] 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 d1db5aed9..4922b035f 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) } From c48a5bf42b2584a08605fb8c36dbfc740aa65a4e Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 19 Mar 2015 16:45:07 -0500 Subject: [PATCH 048/295] provider/aws: Fix DB Subnet refresh issue --- builtin/providers/aws/resource_aws_db_subnet_group.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builtin/providers/aws/resource_aws_db_subnet_group.go b/builtin/providers/aws/resource_aws_db_subnet_group.go index d204c5f96..a7bbc7b47 100644 --- a/builtin/providers/aws/resource_aws_db_subnet_group.go +++ b/builtin/providers/aws/resource_aws_db_subnet_group.go @@ -79,6 +79,11 @@ func resourceAwsDbSubnetGroupRead(d *schema.ResourceData, meta interface{}) erro describeResp, err := rdsconn.DescribeDBSubnetGroups(&describeOpts) if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "DBSubnetGroupNotFoundFault" { + // Update state to indicate the db subnet no longer exists. + d.SetId("") + return nil + } return err } From f97343dea15335af23a2dc5bfa291e2b81ccf77d Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 20 Mar 2015 10:11:12 -0500 Subject: [PATCH 049/295] provider/aws: Fix issue in AWS AutoScaling Group where health_check_type is not set correctly --- builtin/providers/aws/resource_aws_autoscaling_group.go | 2 +- builtin/providers/aws/resource_aws_autoscaling_group_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/providers/aws/resource_aws_autoscaling_group.go b/builtin/providers/aws/resource_aws_autoscaling_group.go index efabb1638..2950e252d 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group.go @@ -137,7 +137,7 @@ func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) autoScalingGroupOpts.DefaultCooldown = aws.Integer(v.(int)) } - if v, ok := d.GetOk("health_check"); ok && v.(string) != "" { + if v, ok := d.GetOk("health_check_type"); ok && v.(string) != "" { autoScalingGroupOpts.HealthCheckType = aws.String(v.(string)) } diff --git a/builtin/providers/aws/resource_aws_autoscaling_group_test.go b/builtin/providers/aws/resource_aws_autoscaling_group_test.go index d940bb40d..2a7e053be 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group_test.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group_test.go @@ -130,7 +130,7 @@ func testAccCheckAWSAutoScalingGroupAttributes(group *autoscaling.AutoScalingGro } if *group.HealthCheckType != "ELB" { - return fmt.Errorf("Bad health_check_type: %s", *group.HealthCheckType) + return fmt.Errorf("Bad health_check_type,\nexpected: %s\ngot: %s", "ELB", *group.HealthCheckType) } if *group.HealthCheckGracePeriod != 300 { From 9545f26fa01c67d2339cb80163a9a0392c7e1e2a Mon Sep 17 00:00:00 2001 From: Phil Frost Date: Fri, 20 Mar 2015 13:29:11 -0400 Subject: [PATCH 050/295] Correct AWS VPC or route table read functions If the state file contained a VPC or a route table which no longer exists, Terraform would fail to create the correct plan, which is to recreate them. In the case of VPCs, this was due to incorrect error handling. The AWS SDK returns a aws.APIError, not a *aws.APIError on error. When the VPC no longer exists, upon attempting to refresh state Terraform would simply exit with an error. For route tables, the provider would recognize that the route table no longer existed, but would not make the appropriate call to update the state as such. Thus there'd be no crash, but also no plan to re-create the route table. --- builtin/providers/aws/resource_aws_route_table.go | 1 + builtin/providers/aws/resource_aws_vpc.go | 4 ++-- builtin/providers/aws/resource_aws_vpc_test.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_route_table.go b/builtin/providers/aws/resource_aws_route_table.go index 6a4b1d3ca..5574e93b2 100644 --- a/builtin/providers/aws/resource_aws_route_table.go +++ b/builtin/providers/aws/resource_aws_route_table.go @@ -107,6 +107,7 @@ func resourceAwsRouteTableRead(d *schema.ResourceData, meta interface{}) error { return err } if rtRaw == nil { + d.SetId("") return nil } diff --git a/builtin/providers/aws/resource_aws_vpc.go b/builtin/providers/aws/resource_aws_vpc.go index 0ef8aa570..727287039 100644 --- a/builtin/providers/aws/resource_aws_vpc.go +++ b/builtin/providers/aws/resource_aws_vpc.go @@ -238,7 +238,7 @@ func resourceAwsVpcDelete(d *schema.ResourceData, meta interface{}) error { } log.Printf("[INFO] Deleting VPC: %s", d.Id()) if err := ec2conn.DeleteVPC(DeleteVpcOpts); err != nil { - ec2err, ok := err.(*aws.APIError) + ec2err, ok := err.(aws.APIError) if ok && ec2err.Code == "InvalidVpcID.NotFound" { return nil } @@ -258,7 +258,7 @@ func VPCStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { } resp, err := conn.DescribeVPCs(DescribeVpcOpts) if err != nil { - if ec2err, ok := err.(*aws.APIError); ok && ec2err.Code == "InvalidVpcID.NotFound" { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpcID.NotFound" { resp = nil } else { log.Printf("Error on VPCStateRefresh: %s", err) diff --git a/builtin/providers/aws/resource_aws_vpc_test.go b/builtin/providers/aws/resource_aws_vpc_test.go index 092f47806..ab97249a2 100644 --- a/builtin/providers/aws/resource_aws_vpc_test.go +++ b/builtin/providers/aws/resource_aws_vpc_test.go @@ -132,7 +132,7 @@ func testAccCheckVpcDestroy(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 } From b49fba6b61886a4226738325ddd86d939f9351ff Mon Sep 17 00:00:00 2001 From: Phil Frost Date: Fri, 20 Mar 2015 14:44:42 -0400 Subject: [PATCH 051/295] Don't error when enabling DNS hostnames in a VPC The AWS API call ModifyVpcAttribute will allow only one attribute to be modified at a time. Modifying both results in the error: Fields for multiple attribute types specified: enableDnsHostnames, enableDnsSupport Retructure the provider to honor this restriction. Also, enable DNS support before attempting to enable DNS hostnames, since the former is a prerequisite of the latter. Additionally, fix what must have been a copy&paste error, setting enable_dns_support to the value of enable_dns_hostnames. --- builtin/providers/aws/resource_aws_vpc.go | 46 ++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpc.go b/builtin/providers/aws/resource_aws_vpc.go index 727287039..7b27f3b6e 100644 --- a/builtin/providers/aws/resource_aws_vpc.go +++ b/builtin/providers/aws/resource_aws_vpc.go @@ -185,29 +185,14 @@ func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error { // Turn on partial mode d.Partial(true) vpcid := d.Id() - modifyOpts := &ec2.ModifyVPCAttributeRequest{ - VPCID: &vpcid, - } - if d.HasChange("enable_dns_hostnames") { - val := d.Get("enable_dns_hostnames").(bool) - modifyOpts.EnableDNSHostnames = &ec2.AttributeBooleanValue{ - Value: &val, - } - - log.Printf( - "[INFO] Modifying enable_dns_hostnames vpc attribute for %s: %#v", - d.Id(), modifyOpts) - if err := ec2conn.ModifyVPCAttribute(modifyOpts); err != nil { - return err - } - - d.SetPartial("enable_dns_hostnames") - } if d.HasChange("enable_dns_support") { - val := d.Get("enable_dns_hostnames").(bool) - modifyOpts.EnableDNSSupport = &ec2.AttributeBooleanValue{ - Value: &val, + val := d.Get("enable_dns_support").(bool) + modifyOpts := &ec2.ModifyVPCAttributeRequest{ + VPCID: &vpcid, + EnableDNSSupport: &ec2.AttributeBooleanValue{ + Value: &val, + }, } log.Printf( @@ -220,6 +205,25 @@ func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error { d.SetPartial("enable_dns_support") } + if d.HasChange("enable_dns_hostnames") { + val := d.Get("enable_dns_hostnames").(bool) + modifyOpts := &ec2.ModifyVPCAttributeRequest{ + VPCID: &vpcid, + EnableDNSHostnames: &ec2.AttributeBooleanValue{ + Value: &val, + }, + } + + log.Printf( + "[INFO] Modifying enable_dns_hostnames vpc attribute for %s: %#v", + d.Id(), modifyOpts) + if err := ec2conn.ModifyVPCAttribute(modifyOpts); err != nil { + return err + } + + d.SetPartial("enable_dns_hostnames") + } + if err := setTags(ec2conn, d); err != nil { return err } else { From 85fc1bca330b417262f249a2ce8150e87549841a Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Sat, 21 Mar 2015 09:16:16 -0500 Subject: [PATCH 052/295] 'project' should be set to the project's ID, not its name. --- website/source/docs/providers/google/index.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/providers/google/index.html.markdown b/website/source/docs/providers/google/index.html.markdown index cacf7599b..6ba08b844 100644 --- a/website/source/docs/providers/google/index.html.markdown +++ b/website/source/docs/providers/google/index.html.markdown @@ -40,7 +40,7 @@ The following keys can be used to configure the provider. are running terraform from a GCE instance with a properly-configured [Compute Engine Service Account](https://cloud.google.com/compute/docs/authentication). -* `project` - (Required) The name of the project to apply any resources to. +* `project` - (Required) The ID of the project to apply any resources to. * `region` - (Required) The region to operate under. From bbda2d6733a8df0c935c6cacdfe9d4ec11e9ff44 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Sun, 22 Mar 2015 13:29:29 +0000 Subject: [PATCH 053/295] Fix fmt modifiers for bool to be actually bool, not integer --- builtin/providers/aws/structure_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/providers/aws/structure_test.go b/builtin/providers/aws/structure_test.go index ff3d20305..aafdeabbc 100644 --- a/builtin/providers/aws/structure_test.go +++ b/builtin/providers/aws/structure_test.go @@ -409,11 +409,11 @@ func TestExpandPrivateIPAddesses(t *testing.T) { } 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) + t.Fatalf("expected ip to be 192.168.0.1 and Primary, but got %v, %t", *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) + t.Fatalf("expected ip to be 192.168.0.2 and not Primary, but got %v, %t", *result[1].PrivateIPAddress, *result[1].Primary) } } From a4e80b6313437fb70f2a2533585cb17d9c1b4ea7 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Mon, 23 Mar 2015 11:58:45 -0500 Subject: [PATCH 054/295] providers/aws: derive instance root_block_device name I was working on building a validation to check the user-provided "device_name" for "root_block_device" on AWS Instances, when I realized that if I can check it, I might as well just derive it automatically! So that's what we do here - when you customize the details of the root block device, device name is just comes from the selected AMI. --- .../providers/aws/resource_aws_instance.go | 44 ++++++++++++------- .../aws/resource_aws_instance_test.go | 7 +-- .../providers/aws/r/instance.html.markdown | 3 -- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index fff106148..69c627862 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -265,13 +265,6 @@ func resourceAwsInstance() *schema.Resource { ForceNew: true, }, - "device_name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "/dev/sda1", - }, - "iops": &schema.Schema{ Type: schema.TypeInt, Optional: true, @@ -298,7 +291,6 @@ func resourceAwsInstance() *schema.Resource { 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))) @@ -478,10 +470,14 @@ func resourceAwsInstanceCreate(d *schema.ResourceData, meta interface{}) error { ebs.IOPS = aws.Integer(v) } - blockDevices = append(blockDevices, ec2.BlockDeviceMapping{ - DeviceName: aws.String(bd["device_name"].(string)), - EBS: ebs, - }) + if dn, err := fetchRootDeviceName(d.Get("ami").(string), ec2conn); err == nil { + blockDevices = append(blockDevices, ec2.BlockDeviceMapping{ + DeviceName: dn, + EBS: ebs, + }) + } else { + return err + } } } @@ -778,9 +774,6 @@ func readBlockDevicesFromInstance(instance *ec2.Instance, ec2conn *ec2.EC2) (map 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 } @@ -794,6 +787,9 @@ func readBlockDevicesFromInstance(instance *ec2.Instance, ec2conn *ec2.EC2) (map if blockDeviceIsRoot(instanceBd, instance) { blockDevices["root"] = bd } else { + if instanceBd.DeviceName != nil { + bd["device_name"] = *instanceBd.DeviceName + } if vol.Encrypted != nil { bd["encrypted"] = *vol.Encrypted } @@ -813,3 +809,21 @@ func blockDeviceIsRoot(bd ec2.InstanceBlockDeviceMapping, instance *ec2.Instance instance.RootDeviceName != nil && *bd.DeviceName == *instance.RootDeviceName) } + +func fetchRootDeviceName(ami string, conn *ec2.EC2) (aws.StringValue, error) { + if ami == "" { + return nil, fmt.Errorf("Cannot fetch root device name for blank AMI ID.") + } + + log.Printf("[DEBUG] Describing AMI %q to get root block device name", ami) + req := &ec2.DescribeImagesRequest{ImageIDs: []string{ami}} + if res, err := conn.DescribeImages(req); err == nil { + if len(res.Images) == 1 { + return res.Images[0].RootDeviceName, nil + } else { + return nil, fmt.Errorf("Expected 1 AMI for ID: %s, got: %#v", ami, res.Images) + } + } else { + return nil, err + } +} diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index 638fceb37..fab8aeed1 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -140,11 +140,9 @@ func TestAccAWSInstance_blockDevices(t *testing.T) { resource.TestCheckResourceAttr( "aws_instance.foo", "root_block_device.#", "1"), resource.TestCheckResourceAttr( - "aws_instance.foo", "root_block_device.1246122048.device_name", "/dev/sda1"), + "aws_instance.foo", "root_block_device.1023169747.volume_size", "11"), resource.TestCheckResourceAttr( - "aws_instance.foo", "root_block_device.1246122048.volume_size", "11"), - resource.TestCheckResourceAttr( - "aws_instance.foo", "root_block_device.1246122048.volume_type", "gp2"), + "aws_instance.foo", "root_block_device.1023169747.volume_type", "gp2"), resource.TestCheckResourceAttr( "aws_instance.foo", "ebs_block_device.#", "2"), resource.TestCheckResourceAttr( @@ -467,7 +465,6 @@ resource "aws_instance" "foo" { instance_type = "m1.small" root_block_device { - device_name = "/dev/sda1" volume_type = "gp2" volume_size = 11 } diff --git a/website/source/docs/providers/aws/r/instance.html.markdown b/website/source/docs/providers/aws/r/instance.html.markdown index 545dff44b..07189565a 100644 --- a/website/source/docs/providers/aws/r/instance.html.markdown +++ b/website/source/docs/providers/aws/r/instance.html.markdown @@ -66,9 +66,6 @@ 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 - is the typical root volume for Linux instances. * `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. From 32eebf4e15aaa6cdb092878cd0a57647b91ff7e9 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Mon, 23 Mar 2015 15:01:53 -0500 Subject: [PATCH 055/295] provider/aws: Cleanup Route 53 subdomain name handling --- .../aws/resource_aws_route53_record.go | 63 +++++++++++++------ .../aws/resource_aws_route53_record_test.go | 27 +++++++- 2 files changed, 68 insertions(+), 22 deletions(-) diff --git a/builtin/providers/aws/resource_aws_route53_record.go b/builtin/providers/aws/resource_aws_route53_record.go index a276ac463..71ddff056 100644 --- a/builtin/providers/aws/resource_aws_route53_record.go +++ b/builtin/providers/aws/resource_aws_route53_record.go @@ -67,17 +67,8 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er return err } - // Check if the current record name contains the zone suffix. - // If it does not, add the zone name to form a fully qualified name - // and keep AWS happy. - recordName := d.Get("name").(string) - zoneName := strings.Trim(*zoneRecord.HostedZone.Name, ".") - if !strings.HasSuffix(recordName, zoneName) { - d.Set("name", strings.Join([]string{recordName, zoneName}, ".")) - } - // Get the record - rec, err := resourceAwsRoute53RecordBuildSet(d) + rec, err := resourceAwsRoute53RecordBuildSet(d, *zoneRecord.HostedZone.Name) if err != nil { return err } @@ -101,7 +92,7 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er } log.Printf("[DEBUG] Creating resource records for zone: %s, name: %s", - zone, d.Get("name").(string)) + zone, *rec.Name) wait := resource.StateChangeConf{ Pending: []string{"rejected"}, @@ -111,10 +102,12 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er Refresh: func() (interface{}, string, error) { resp, 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 nil, "rejected", nil + if r53err, ok := err.(aws.APIError); ok { + if r53err.Code == "PriorRequestNotComplete" { + // There is some pending operation, so just retry + // in a bit. + return nil, "rejected", nil + } } return nil, "failure", err @@ -159,9 +152,17 @@ func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) erro conn := meta.(*AWSClient).r53conn zone := d.Get("zone_id").(string) + + // get expanded name + zoneRecord, err := conn.GetHostedZone(&route53.GetHostedZoneRequest{ID: aws.String(zone)}) + if err != nil { + return err + } + en := expandRecordName(d.Get("name").(string), *zoneRecord.HostedZone.Name) + lopts := &route53.ListResourceRecordSetsRequest{ HostedZoneID: aws.String(cleanZoneID(zone)), - StartRecordName: aws.String(d.Get("name").(string)), + StartRecordName: aws.String(en), StartRecordType: aws.String(d.Get("type").(string)), } @@ -202,9 +203,12 @@ func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) er zone := d.Get("zone_id").(string) log.Printf("[DEBUG] Deleting resource records for zone: %s, name: %s", zone, d.Get("name").(string)) - + zoneRecord, err := conn.GetHostedZone(&route53.GetHostedZoneRequest{ID: aws.String(zone)}) + if err != nil { + return err + } // Get the records - rec, err := resourceAwsRoute53RecordBuildSet(d) + rec, err := resourceAwsRoute53RecordBuildSet(d, *zoneRecord.HostedZone.Name) if err != nil { return err } @@ -260,7 +264,7 @@ func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) er return nil } -func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData) (*route53.ResourceRecordSet, error) { +func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData, zoneName string) (*route53.ResourceRecordSet, error) { recs := d.Get("records").(*schema.Set).List() records := make([]route53.ResourceRecord, 0, len(recs)) @@ -275,8 +279,15 @@ func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData) (*route53.Resource } } + // get expanded name + en := expandRecordName(d.Get("name").(string), zoneName) + + // Create the RecordSet request with the fully expanded name, e.g. + // sub.domain.com. Route 53 requires a fully qualified domain name, but does + // not require the trailing ".", which it will itself, so we don't call FQDN + // here. rec := &route53.ResourceRecordSet{ - Name: aws.String(d.Get("name").(string)), + Name: aws.String(en), Type: aws.String(d.Get("type").(string)), TTL: aws.Long(int64(d.Get("ttl").(int))), ResourceRecords: records, @@ -304,3 +315,15 @@ func cleanRecordName(name string) string { } return str } + +// Check if the current record name contains the zone suffix. +// If it does not, add the zone name to form a fully qualified name +// and keep AWS happy. +func expandRecordName(name, zone string) string { + rn := strings.TrimSuffix(name, ".") + zone = strings.TrimSuffix(zone, ".") + if !strings.HasSuffix(rn, zone) { + rn = strings.Join([]string{name, zone}, ".") + } + return rn +} diff --git a/builtin/providers/aws/resource_aws_route53_record_test.go b/builtin/providers/aws/resource_aws_route53_record_test.go index ba2da3e05..8aa0a1e56 100644 --- a/builtin/providers/aws/resource_aws_route53_record_test.go +++ b/builtin/providers/aws/resource_aws_route53_record_test.go @@ -29,6 +29,27 @@ func TestCleanRecordName(t *testing.T) { } } +func TestExpandRecordName(t *testing.T) { + cases := []struct { + Input, Output string + }{ + {"www", "www.nonexample.com"}, + {"dev.www", "dev.www.nonexample.com"}, + {"*", "*.nonexample.com"}, + {"nonexample.com", "nonexample.com"}, + {"test.nonexample.com", "test.nonexample.com"}, + {"test.nonexample.com.", "test.nonexample.com"}, + } + + zone_name := "nonexample.com" + for _, tc := range cases { + actual := expandRecordName(tc.Input, zone_name) + 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) }, @@ -151,9 +172,11 @@ func testAccCheckRoute53RecordExists(n string) resource.TestCheckFunc { name := parts[1] rType := parts[2] + en := expandRecordName(name, "notexample.com") + lopts := &route53.ListResourceRecordSetsRequest{ HostedZoneID: aws.String(cleanZoneID(zone)), - StartRecordName: aws.String(name), + StartRecordName: aws.String(en), StartRecordType: aws.String(rType), } @@ -167,7 +190,7 @@ func testAccCheckRoute53RecordExists(n string) resource.TestCheckFunc { // rec := resp.ResourceRecordSets[0] for _, rec := range resp.ResourceRecordSets { recName := cleanRecordName(*rec.Name) - if FQDN(recName) == FQDN(name) && *rec.Type == rType { + if FQDN(recName) == FQDN(en) && *rec.Type == rType { return nil } } From 9d75fa72aba677e84dc4638a13de7692d72d9051 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Tue, 24 Mar 2015 11:45:20 -0500 Subject: [PATCH 056/295] Add disk size to google_compute_instance disk blocks. --- builtin/providers/google/resource_compute_instance.go | 11 +++++++++++ .../providers/google/r/compute_instance.html.markdown | 3 +++ 2 files changed, 14 insertions(+) diff --git a/builtin/providers/google/resource_compute_instance.go b/builtin/providers/google/resource_compute_instance.go index 3b3e86ded..572d77310 100644 --- a/builtin/providers/google/resource_compute_instance.go +++ b/builtin/providers/google/resource_compute_instance.go @@ -74,6 +74,12 @@ func resourceComputeInstance() *schema.Resource { Optional: true, ForceNew: true, }, + + "size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, }, }, }, @@ -331,6 +337,11 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err disk.InitializeParams.DiskType = diskType.SelfLink } + if v, ok := d.GetOk(prefix + ".size"); ok { + diskSizeGb := v.(int) + disk.InitializeParams.DiskSizeGb = int64(diskSizeGb) + } + disks = append(disks, &disk) } diff --git a/website/source/docs/providers/google/r/compute_instance.html.markdown b/website/source/docs/providers/google/r/compute_instance.html.markdown index 5c6cfe027..3d3104d17 100644 --- a/website/source/docs/providers/google/r/compute_instance.html.markdown +++ b/website/source/docs/providers/google/r/compute_instance.html.markdown @@ -93,6 +93,9 @@ The `disk` block supports: * `type` - (Optional) The GCE disk type. +* `size` - (Optional) The size of the image in gigabytes. If not specified, + it will inherit the size of its base image. + The `network_interface` block supports: * `network` - (Required) The name of the network to attach this interface to. From ef094e2cfef2c3930d4ae07c24a537cb35419c11 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 24 Mar 2015 13:37:42 -0500 Subject: [PATCH 057/295] provider/aws: Add tag support to ELB --- builtin/providers/aws/resource_aws_elb.go | 21 ++++- .../providers/aws/resource_aws_elb_test.go | 80 ++++++++++++++++ builtin/providers/aws/tagsELB.go | 94 +++++++++++++++++++ builtin/providers/aws/tagsELB_test.go | 85 +++++++++++++++++ 4 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/aws/tagsELB.go create mode 100644 builtin/providers/aws/tagsELB_test.go diff --git a/builtin/providers/aws/resource_aws_elb.go b/builtin/providers/aws/resource_aws_elb.go index 7898f2120..b15fe1afa 100644 --- a/builtin/providers/aws/resource_aws_elb.go +++ b/builtin/providers/aws/resource_aws_elb.go @@ -154,6 +154,8 @@ func resourceAwsElb() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "tags": tagsSchema(), }, } } @@ -167,11 +169,12 @@ func resourceAwsElbCreate(d *schema.ResourceData, meta interface{}) error { return err } + tags := tagsFromMapELB(d.Get("tags").(map[string]interface{})) // Provision the elb - elbOpts := &elb.CreateAccessPointInput{ LoadBalancerName: aws.String(d.Get("name").(string)), Listeners: listeners, + Tags: tags, } if scheme, ok := d.GetOk("internal"); ok && scheme.(bool) { @@ -208,6 +211,8 @@ func resourceAwsElbCreate(d *schema.ResourceData, meta interface{}) error { d.SetPartial("security_groups") d.SetPartial("subnets") + d.Set("tags", tagsToMapELB(tags)) + if d.HasChange("health_check") { vs := d.Get("health_check").(*schema.Set).List() if len(vs) > 0 { @@ -267,6 +272,15 @@ func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error { d.Set("security_groups", lb.SecurityGroups) d.Set("subnets", lb.Subnets) + resp, err := elbconn.DescribeTags(&elb.DescribeTagsInput{ + LoadBalancerNames: []string{*lb.LoadBalancerName}, + }) + + var et []elb.Tag + if len(resp.TagDescriptions) > 0 { + et = resp.TagDescriptions[0].Tags + } + d.Set("tags", tagsToMapELB(et)) // There's only one health check, so save that to state as we // currently can if *lb.HealthCheck.Target != "" { @@ -357,6 +371,11 @@ func resourceAwsElbUpdate(d *schema.ResourceData, meta interface{}) error { } } + if err := setTagsELB(elbconn, d); err != nil { + return err + } else { + d.SetPartial("tags") + } d.Partial(false) return resourceAwsElbRead(d, meta) diff --git a/builtin/providers/aws/resource_aws_elb_test.go b/builtin/providers/aws/resource_aws_elb_test.go index 037a9557d..2fbe7ace8 100644 --- a/builtin/providers/aws/resource_aws_elb_test.go +++ b/builtin/providers/aws/resource_aws_elb_test.go @@ -53,6 +53,61 @@ func TestAccAWSELB_basic(t *testing.T) { }) } +func TestAccAWSELB_tags(t *testing.T) { + var conf elb.LoadBalancerDescription + var td elb.TagDescription + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSELBDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSELBConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSELBExists("aws_elb.bar", &conf), + testAccCheckAWSELBAttributes(&conf), + resource.TestCheckResourceAttr( + "aws_elb.bar", "name", "foobar-terraform-test"), + testAccLoadTags(&conf, &td), + testAccCheckELBTags(&td.Tags, "bar", "baz"), + ), + }, + + resource.TestStep{ + Config: testAccAWSELBConfig_TagUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSELBExists("aws_elb.bar", &conf), + testAccCheckAWSELBAttributes(&conf), + resource.TestCheckResourceAttr( + "aws_elb.bar", "name", "foobar-terraform-test"), + testAccLoadTags(&conf, &td), + testAccCheckELBTags(&td.Tags, "foo", "bar"), + testAccCheckELBTags(&td.Tags, "new", "type"), + ), + }, + }, + }) +} + +func testAccLoadTags(conf *elb.LoadBalancerDescription, td *elb.TagDescription) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).elbconn + + describe, err := conn.DescribeTags(&elb.DescribeTagsInput{ + LoadBalancerNames: []string{*conf.LoadBalancerName}, + }) + + if err != nil { + return err + } + if len(describe.TagDescriptions) > 0 { + *td = describe.TagDescriptions[0] + } + return nil + } +} + func TestAccAWSELB_InstanceAttaching(t *testing.T) { var conf elb.LoadBalancerDescription @@ -288,6 +343,31 @@ resource "aws_elb" "bar" { lb_protocol = "http" } + tags { + bar = "baz" + } + + cross_zone_load_balancing = true +} +` + +const testAccAWSELBConfig_TagUpdate = ` +resource "aws_elb" "bar" { + name = "foobar-terraform-test" + availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"] + + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } + + tags { + foo = "bar" + new = "type" + } + cross_zone_load_balancing = true } ` diff --git a/builtin/providers/aws/tagsELB.go b/builtin/providers/aws/tagsELB.go new file mode 100644 index 000000000..ad5e0752e --- /dev/null +++ b/builtin/providers/aws/tagsELB.go @@ -0,0 +1,94 @@ +package aws + +import ( + "log" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/elb" + "github.com/hashicorp/terraform/helper/schema" +) + +// setTags is a helper to set the tags for a resource. It expects the +// tags field to be named "tags" +func setTagsELB(conn *elb.ELB, d *schema.ResourceData) error { + if d.HasChange("tags") { + oraw, nraw := d.GetChange("tags") + o := oraw.(map[string]interface{}) + n := nraw.(map[string]interface{}) + create, remove := diffTagsELB(tagsFromMapELB(o), tagsFromMapELB(n)) + + // Set tags + if len(remove) > 0 { + log.Printf("[DEBUG] Removing tags: %#v", remove) + k := make([]elb.TagKeyOnly, 0, len(remove)) + for _, t := range remove { + k = append(k, elb.TagKeyOnly{Key: t.Key}) + } + _, err := conn.RemoveTags(&elb.RemoveTagsInput{ + LoadBalancerNames: []string{d.Get("name").(string)}, + Tags: k, + }) + if err != nil { + return err + } + } + if len(create) > 0 { + log.Printf("[DEBUG] Creating tags: %#v", create) + _, err := conn.AddTags(&elb.AddTagsInput{ + LoadBalancerNames: []string{d.Get("name").(string)}, + Tags: create, + }) + if err != nil { + return err + } + } + } + + return nil +} + +// diffTags takes our tags locally and the ones remotely and returns +// the set of tags that must be created, and the set of tags that must +// be destroyed. +func diffTagsELB(oldTags, newTags []elb.Tag) ([]elb.Tag, []elb.Tag) { + // First, we're creating everything we have + create := make(map[string]interface{}) + for _, t := range newTags { + create[*t.Key] = *t.Value + } + + // Build the list of what to remove + var remove []elb.Tag + for _, t := range oldTags { + old, ok := create[*t.Key] + if !ok || old != *t.Value { + // Delete it! + remove = append(remove, t) + } + } + + return tagsFromMapELB(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMapELB(m map[string]interface{}) []elb.Tag { + result := make([]elb.Tag, 0, len(m)) + for k, v := range m { + result = append(result, elb.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + }) + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMapELB(ts []elb.Tag) map[string]string { + result := make(map[string]string) + for _, t := range ts { + result[*t.Key] = *t.Value + } + + return result +} diff --git a/builtin/providers/aws/tagsELB_test.go b/builtin/providers/aws/tagsELB_test.go new file mode 100644 index 000000000..79021b4dd --- /dev/null +++ b/builtin/providers/aws/tagsELB_test.go @@ -0,0 +1,85 @@ +package aws + +import ( + "fmt" + "reflect" + "testing" + + "github.com/hashicorp/aws-sdk-go/gen/elb" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestDiffELBTags(t *testing.T) { + cases := []struct { + Old, New map[string]interface{} + Create, Remove map[string]string + }{ + // Basic add/remove + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "bar": "baz", + }, + Create: map[string]string{ + "bar": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + + // Modify + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "foo": "baz", + }, + Create: map[string]string{ + "foo": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + } + + for i, tc := range cases { + c, r := diffTagsELB(tagsFromMapELB(tc.Old), tagsFromMapELB(tc.New)) + cm := tagsToMapELB(c) + rm := tagsToMapELB(r) + if !reflect.DeepEqual(cm, tc.Create) { + t.Fatalf("%d: bad create: %#v", i, cm) + } + if !reflect.DeepEqual(rm, tc.Remove) { + t.Fatalf("%d: bad remove: %#v", i, rm) + } + } +} + +// testAccCheckTags can be used to check the tags on a resource. +func testAccCheckELBTags( + ts *[]elb.Tag, key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + m := tagsToMapELB(*ts) + v, ok := m[key] + if value != "" && !ok { + return fmt.Errorf("Missing tag: %s", key) + } else if value == "" && ok { + return fmt.Errorf("Extra tag: %s", key) + } + if value == "" { + return nil + } + + if v != value { + return fmt.Errorf("%s: bad value: %s", key, v) + } + + return nil + } +} From cacc7a5d6fb1e5aba5e6452bb02da2fc59078489 Mon Sep 17 00:00:00 2001 From: Spencer Nelson Date: Tue, 24 Mar 2015 12:06:09 -0700 Subject: [PATCH 058/295] Bring usage docs up to date --- website/source/intro/getting-started/install.html.markdown | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/source/intro/getting-started/install.html.markdown b/website/source/intro/getting-started/install.html.markdown index dc4729bc9..629e97a57 100644 --- a/website/source/intro/getting-started/install.html.markdown +++ b/website/source/intro/getting-started/install.html.markdown @@ -40,11 +40,16 @@ usage: terraform [--version] [--help] [] Available commands are: apply Builds or changes infrastructure + destroy Destroy Terraform-managed infrastructure + get Download and install modules for the configuration graph Create a visual graph of Terraform resources + init Initializes Terraform configuration from a module output Read an output from a state file plan Generate and show an execution plan refresh Update local state file against real resources + remote Configure remote state storage show Inspect Terraform state or plan + taint Manually mark a resource for recreation version Prints the Terraform version ``` From 2dce764d757d391c3e7df9c9f35275bdae7c46a0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Mar 2015 15:04:12 -0800 Subject: [PATCH 059/295] terraform: add input mode to only ask for unset variables This adds a new input mode for Context.Input() that will only ask for variable values that are not set. --- terraform/context.go | 28 ++++++++++++- terraform/context_test.go | 42 +++++++++++++++++++ terraform/terraform_test.go | 8 ++++ .../test-fixtures/input-vars-unset/main.tf | 7 ++++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 terraform/test-fixtures/input-vars-unset/main.tf diff --git a/terraform/context.go b/terraform/context.go index e2db6e6f9..62a06f431 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -16,9 +16,12 @@ import ( type InputMode byte const ( - // InputModeVar asks for variables + // InputModeVar asks for all variables InputModeVar InputMode = 1 << iota + // InputModeVarUnset asks for variables which are not set yet + InputModeVarUnset + // InputModeProvider asks for provider variables InputModeProvider @@ -154,6 +157,14 @@ func (c *Context) Input(mode InputMode) error { } sort.Strings(names) for _, n := range names { + // If we only care about unset variables, then if the variabel + // is set, continue on. + if mode&InputModeVarUnset != 0 { + if _, ok := c.variables[n]; ok { + continue + } + } + v := m[n] switch v.Type() { case config.VariableTypeMap: @@ -365,6 +376,21 @@ func (c *Context) Validate() ([]string, []error) { return walker.ValidationWarnings, rerrs.Errors } +<<<<<<< Updated upstream +======= +// Variables will return the mapping of variables that were defined +// for this Context. If Input was called, this mapping may be different +// than what was given. +func (c *Context) Variables() map[string]string { + return c.variables +} + +// SetVariable sets a variable after a context has already been built. +func (c *Context) SetVariable(k, v string) { + c.variables[k] = v +} + +>>>>>>> Stashed changes func (c *Context) acquireRun() chan<- struct{} { c.l.Lock() defer c.l.Unlock() diff --git a/terraform/context_test.go b/terraform/context_test.go index 9050d4b96..c6dd83b94 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -2758,6 +2758,48 @@ func TestContext2Input_varOnly(t *testing.T) { } } +func TestContext2Input_varOnlyUnset(t *testing.T) { + input := new(MockUIInput) + m := testModule(t, "input-vars-unset") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Variables: map[string]string{ + "foo": "foovalue", + }, + UIInput: input, + }) + + input.InputReturnMap = map[string]string{ + "var.foo": "nope", + "var.bar": "baz", + } + + if err := ctx.Input(InputModeVar | InputModeVarUnset); err != nil { + t.Fatalf("err: %s", err) + } + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actualStr := strings.TrimSpace(state.String()) + expectedStr := strings.TrimSpace(testTerraformInputVarOnlyUnsetStr) + if actualStr != expectedStr { + t.Fatalf("bad: \n%s", actualStr) + } +} + func TestContext2Apply(t *testing.T) { m := testModule(t, "apply-good") p := testProvider("aws") diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 94664791f..b3c0f9529 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -150,6 +150,14 @@ aws_instance.foo: type = aws_instance ` +const testTerraformInputVarOnlyUnsetStr = ` +aws_instance.foo: + ID = foo + bar = baz + foo = foovalue + type = aws_instance +` + const testTerraformInputVarsStr = ` aws_instance.bar: ID = foo diff --git a/terraform/test-fixtures/input-vars-unset/main.tf b/terraform/test-fixtures/input-vars-unset/main.tf new file mode 100644 index 000000000..28cf230e6 --- /dev/null +++ b/terraform/test-fixtures/input-vars-unset/main.tf @@ -0,0 +1,7 @@ +variable "foo" {} +variable "bar" {} + +resource "aws_instance" "foo" { + foo = "${var.foo}" + bar = "${var.bar}" +} From cdde9149ff50425f0da5fee6ece40e87a60f6596 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Mar 2015 20:42:26 -0800 Subject: [PATCH 060/295] command/push: start it --- command/push.go | 104 +++++++++++++++++++++++++++++++++++++++++++ command/push_test.go | 59 ++++++++++++++++++++++++ commands.go | 6 +++ 3 files changed, 169 insertions(+) create mode 100644 command/push.go create mode 100644 command/push_test.go diff --git a/command/push.go b/command/push.go new file mode 100644 index 000000000..dbc498991 --- /dev/null +++ b/command/push.go @@ -0,0 +1,104 @@ +package command + +import ( + "flag" + "fmt" + "os" + "strings" +) + +type PushCommand struct { + Meta +} + +func (c *PushCommand) Run(args []string) int { + var atlasToken string + var moduleLock bool + args = c.Meta.process(args, false) + cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError) + cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") + cmdFlags.StringVar(&atlasToken, "token", "", "") + cmdFlags.BoolVar(&moduleLock, "module-lock", true, "") + cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } + if err := cmdFlags.Parse(args); err != nil { + return 1 + } + + // The pwd is used for the configuration path if one is not given + pwd, err := os.Getwd() + if err != nil { + c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) + return 1 + } + + // Get the path to the configuration depending on the args. + var configPath string + args = cmdFlags.Args() + if len(args) > 1 { + c.Ui.Error("The apply command expects at most one argument.") + cmdFlags.Usage() + return 1 + } else if len(args) == 1 { + configPath = args[0] + } else { + configPath = pwd + } + + // Verify the state is remote, we can't push without a remote state + s, err := c.State() + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to read state: %s", err)) + return 1 + } + if !s.State().IsRemote() { + c.Ui.Error( + "Remote state is not enabled. For Atlas to run Terraform\n" + + "for you, remote state must be used and configured. Remote\n" + + "state via any backend is accepted, not just Atlas. To\n" + + "configure remote state, use the `terraform remote config`\n" + + "command.") + return 1 + } + + // Build the context based on the arguments given + _, planned, err := c.Context(contextOpts{ + Path: configPath, + StatePath: c.Meta.statePath, + }) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + if planned { + c.Ui.Error( + "A plan file cannot be given as the path to the configuration.\n" + + "A path to a module (directory with configuration) must be given.") + return 1 + } + + return 0 +} + +func (c *PushCommand) Help() string { + helpText := ` +Usage: terraform push [options] [DIR] + + Upload this Terraform module to an Atlas server for remote + infrastructure management. + +Options: + + -module-lock=true If true (default), then the modules are locked at + their current checkout and uploaded completely. This + prevents Atlas from running "terraform get". + + -token= Access token to use to upload. If blank, the ATLAS_TOKEN + environmental variable will be used. + +` + return strings.TrimSpace(helpText) +} + +func (c *PushCommand) Synopsis() string { + return "Upload this Terraform module to Atlas to run" +} diff --git a/command/push_test.go b/command/push_test.go new file mode 100644 index 000000000..8b52421b8 --- /dev/null +++ b/command/push_test.go @@ -0,0 +1,59 @@ +package command + +import ( + "testing" + + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/cli" +) + +func TestPush_noState(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + ui := new(cli.MockUi) + c := &PushCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + } + + args := []string{} + if code := c.Run(args); code != 1 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } +} + +func TestPush_noRemoteState(t *testing.T) { + state := &terraform.State{ + Modules: []*terraform.ModuleState{ + &terraform.ModuleState{ + Path: []string{"root"}, + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + Type: "test_instance", + Primary: &terraform.InstanceState{ + ID: "bar", + }, + }, + }, + }, + }, + } + statePath := testStateFile(t, state) + + ui := new(cli.MockUi) + c := &PushCommand{ + Meta: Meta{ + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + } + if code := c.Run(args); code != 1 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } +} diff --git a/commands.go b/commands.go index c585b7827..a4af7f983 100644 --- a/commands.go +++ b/commands.go @@ -80,6 +80,12 @@ func init() { }, nil }, + "push": func() (cli.Command, error) { + return &command.PushCommand{ + Meta: meta, + }, nil + }, + "refresh": func() (cli.Command, error) { return &command.RefreshCommand{ Meta: meta, From c4dc9af120d0ad3711b9c673af18988e0cf241db Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 4 Mar 2015 20:52:06 -0800 Subject: [PATCH 061/295] command: add DataDir --- command/meta.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/command/meta.go b/command/meta.go index 7cf3ebe05..8a4578b5f 100644 --- a/command/meta.go +++ b/command/meta.go @@ -138,11 +138,7 @@ func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) { return nil, false, fmt.Errorf("Error loading config: %s", err) } - dataDir := DefaultDataDirectory - if m.dataDir != "" { - dataDir = m.dataDir - } - err = mod.Load(m.moduleStorage(dataDir), copts.GetMode) + err = mod.Load(m.moduleStorage(m.DataDir()), copts.GetMode) if err != nil { return nil, false, fmt.Errorf("Error downloading modules: %s", err) } @@ -153,6 +149,16 @@ func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) { return ctx, false, nil } +// DataDir returns the directory where local data will be stored. +func (m *Meta) DataDir() string { + dataDir := DefaultDataDirectory + if m.dataDir != "" { + dataDir = m.dataDir + } + + return dataDir +} + // InputMode returns the type of input we should ask for in the form of // terraform.InputMode which is passed directly to Context.Input. func (m *Meta) InputMode() terraform.InputMode { @@ -205,7 +211,7 @@ func (m *Meta) StateOpts() *StateOpts { if localPath == "" { localPath = DefaultStateFilename } - remotePath := filepath.Join(DefaultDataDir, DefaultStateFilename) + remotePath := filepath.Join(m.DataDir(), DefaultStateFilename) return &StateOpts{ LocalPath: localPath, From ca8e2085f3d99ececcbf68d8776db951b1353c11 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Mar 2015 12:37:13 -0800 Subject: [PATCH 062/295] command/push: archiving --- command/push.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/command/push.go b/command/push.go index dbc498991..cee61b7f6 100644 --- a/command/push.go +++ b/command/push.go @@ -4,7 +4,10 @@ import ( "flag" "fmt" "os" + "path/filepath" "strings" + + "github.com/hashicorp/atlas-go/archive" ) type PushCommand struct { @@ -76,6 +79,27 @@ func (c *PushCommand) Run(args []string) int { return 1 } + // Build the archiving options, which includes everything it can + // by default according to VCS rules but forcing the data directory. + archiveOpts := &archive.ArchiveOpts{ + Include: []string{filepath.Join(c.DataDir())}, + VCS: true, + } + if !moduleLock { + // If we're not locking modules, then exclude the modules dir. + archiveOpts.Exclude = append( + archiveOpts.Exclude, + filepath.Join(c.DataDir(), "modules")) + } + + _, err = archive.CreateArchive(configPath, archiveOpts) + if err != nil { + c.Ui.Error(fmt.Sprintf( + "An error has occurred while archiving the module for uploading:\n"+ + "%s", err)) + return 1 + } + return 0 } From fdded8ca148c9333e3e201a5ccc5250de5f6918b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Mar 2015 12:56:31 -0800 Subject: [PATCH 063/295] config: allow atlas block --- config/append.go | 6 ++++++ config/append_test.go | 9 +++++++++ config/config.go | 8 ++++++++ config/loader_hcl.go | 23 +++++++++++++++++++++++ config/loader_test.go | 11 +++++++++++ config/merge.go | 7 +++++++ config/merge_test.go | 9 +++++++++ config/test-fixtures/basic.tf | 4 ++++ config/test-fixtures/basic.tf.json | 4 ++++ 9 files changed, 81 insertions(+) diff --git a/config/append.go b/config/append.go index f87e67748..bf13534e7 100644 --- a/config/append.go +++ b/config/append.go @@ -21,6 +21,7 @@ func Append(c1, c2 *Config) (*Config, error) { c.unknownKeys = append(c.unknownKeys, k) } } + for _, k := range c2.unknownKeys { _, present := unknowns[k] if !present { @@ -29,6 +30,11 @@ func Append(c1, c2 *Config) (*Config, error) { } } + c.Atlas = c1.Atlas + if c2.Atlas != nil { + c.Atlas = c2.Atlas + } + if len(c1.Modules) > 0 || len(c2.Modules) > 0 { c.Modules = make( []*Module, 0, len(c1.Modules)+len(c2.Modules)) diff --git a/config/append_test.go b/config/append_test.go index e7aea9d21..adeb7835b 100644 --- a/config/append_test.go +++ b/config/append_test.go @@ -12,6 +12,9 @@ func TestAppend(t *testing.T) { }{ { &Config{ + Atlas: &AtlasConfig{ + Name: "foo", + }, Modules: []*Module{ &Module{Name: "foo"}, }, @@ -32,6 +35,9 @@ func TestAppend(t *testing.T) { }, &Config{ + Atlas: &AtlasConfig{ + Name: "bar", + }, Modules: []*Module{ &Module{Name: "bar"}, }, @@ -52,6 +58,9 @@ func TestAppend(t *testing.T) { }, &Config{ + Atlas: &AtlasConfig{ + Name: "bar", + }, Modules: []*Module{ &Module{Name: "foo"}, &Module{Name: "bar"}, diff --git a/config/config.go b/config/config.go index 8dd9810eb..aabc42260 100644 --- a/config/config.go +++ b/config/config.go @@ -28,6 +28,7 @@ type Config struct { // any meaningful directory. Dir string + Atlas *AtlasConfig Modules []*Module ProviderConfigs []*ProviderConfig Resources []*Resource @@ -39,6 +40,13 @@ type Config struct { unknownKeys []string } +// AtlasConfig is the configuration for building in HashiCorp's Atlas. +type AtlasConfig struct { + Name string + Include []string + Exclude []string +} + // Module is a module used within a configuration. // // This does not represent a module itself, this represents a module diff --git a/config/loader_hcl.go b/config/loader_hcl.go index 6c127ea8b..f75f93df3 100644 --- a/config/loader_hcl.go +++ b/config/loader_hcl.go @@ -17,6 +17,7 @@ type hclConfigurable struct { func (t *hclConfigurable) Config() (*Config, error) { validKeys := map[string]struct{}{ + "atlas": struct{}{}, "module": struct{}{}, "output": struct{}{}, "provider": struct{}{}, @@ -70,6 +71,15 @@ func (t *hclConfigurable) Config() (*Config, error) { } } + // Get Atlas configuration + if atlas := t.Object.Get("atlas", false); atlas != nil { + var err error + config.Atlas, err = loadAtlasHcl(atlas) + if err != nil { + return nil, err + } + } + // Build the modules if modules := t.Object.Get("module", false); modules != nil { var err error @@ -187,6 +197,19 @@ func loadFileHcl(root string) (configurable, []string, error) { return result, nil, nil } +// Given a handle to a HCL object, this transforms it into the Atlas +// configuration. +func loadAtlasHcl(obj *hclobj.Object) (*AtlasConfig, error) { + var config AtlasConfig + if err := hcl.DecodeObject(&config, obj); err != nil { + return nil, fmt.Errorf( + "Error reading atlas config: %s", + err) + } + + return &config, nil +} + // Given a handle to a HCL object, this recurses into the structure // and pulls out a list of modules. // diff --git a/config/loader_test.go b/config/loader_test.go index 39fea0296..d487638e9 100644 --- a/config/loader_test.go +++ b/config/loader_test.go @@ -2,6 +2,7 @@ package config import ( "path/filepath" + "reflect" "strings" "testing" ) @@ -57,6 +58,11 @@ func TestLoadBasic(t *testing.T) { t.Fatalf("bad: %#v", c.Dir) } + expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"} + if !reflect.DeepEqual(c.Atlas, expectedAtlas) { + t.Fatalf("bad: %#v", c.Atlas) + } + actual := variablesStr(c.Variables) if actual != strings.TrimSpace(basicVariablesStr) { t.Fatalf("bad:\n%s", actual) @@ -132,6 +138,11 @@ func TestLoadBasic_json(t *testing.T) { t.Fatalf("bad: %#v", c.Dir) } + expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"} + if !reflect.DeepEqual(c.Atlas, expectedAtlas) { + t.Fatalf("bad: %#v", c.Atlas) + } + actual := variablesStr(c.Variables) if actual != strings.TrimSpace(basicVariablesStr) { t.Fatalf("bad:\n%s", actual) diff --git a/config/merge.go b/config/merge.go index c43f13c04..f72fdfa92 100644 --- a/config/merge.go +++ b/config/merge.go @@ -25,6 +25,13 @@ func Merge(c1, c2 *Config) (*Config, error) { } } + // Merge Atlas configuration. This is a dumb one overrides the other + // sort of merge. + c.Atlas = c1.Atlas + if c2.Atlas != nil { + c.Atlas = c2.Atlas + } + // NOTE: Everything below is pretty gross. Due to the lack of generics // in Go, there is some hoop-jumping involved to make this merging a // little more test-friendly and less repetitive. Ironically, making it diff --git a/config/merge_test.go b/config/merge_test.go index 2dbe5aee9..40144f0c7 100644 --- a/config/merge_test.go +++ b/config/merge_test.go @@ -13,6 +13,9 @@ func TestMerge(t *testing.T) { // Normal good case. { &Config{ + Atlas: &AtlasConfig{ + Name: "foo", + }, Modules: []*Module{ &Module{Name: "foo"}, }, @@ -33,6 +36,9 @@ func TestMerge(t *testing.T) { }, &Config{ + Atlas: &AtlasConfig{ + Name: "bar", + }, Modules: []*Module{ &Module{Name: "bar"}, }, @@ -53,6 +59,9 @@ func TestMerge(t *testing.T) { }, &Config{ + Atlas: &AtlasConfig{ + Name: "bar", + }, Modules: []*Module{ &Module{Name: "foo"}, &Module{Name: "bar"}, diff --git a/config/test-fixtures/basic.tf b/config/test-fixtures/basic.tf index 5751a8583..5afadc779 100644 --- a/config/test-fixtures/basic.tf +++ b/config/test-fixtures/basic.tf @@ -49,3 +49,7 @@ resource "aws_instance" "db" { output "web_ip" { value = "${aws_instance.web.private_ip}" } + +atlas { + name = "mitchellh/foo" +} diff --git a/config/test-fixtures/basic.tf.json b/config/test-fixtures/basic.tf.json index 1b946e22b..1013862b3 100644 --- a/config/test-fixtures/basic.tf.json +++ b/config/test-fixtures/basic.tf.json @@ -63,5 +63,9 @@ "web_ip": { "value": "${aws_instance.web.private_ip}" } + }, + + "atlas": { + "name": "mitchellh/foo" } } From 22087181af296b118d34f61f2d015b927d0627c9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Mar 2015 14:55:15 -0800 Subject: [PATCH 064/295] command/push: archive, upload --- command/command_test.go | 21 ++++++ command/push.go | 52 ++++++++++++- command/push_test.go | 116 +++++++++++++++++++++++++++++ command/test-fixtures/push/main.tf | 1 + terraform/context.go | 3 - 5 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 command/test-fixtures/push/main.tf diff --git a/command/command_test.go b/command/command_test.go index 303fc4b2b..2544cf531 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -148,6 +148,27 @@ func testStateFileDefault(t *testing.T, s *terraform.State) string { return DefaultStateFilename } +// testStateFileRemote writes the state out to the remote statefile +// in the cwd. Use `testCwd` to change into a temp cwd. +func testStateFileRemote(t *testing.T, s *terraform.State) string { + path := filepath.Join(DefaultDataDir, DefaultStateFilename) + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + t.Fatalf("err: %s", err) + } + + f, err := os.Create(path) + if err != nil { + t.Fatalf("err: %s", err) + } + defer f.Close() + + if err := terraform.WriteState(s, f); err != nil { + t.Fatalf("err: %s", err) + } + + return path +} + // testStateOutput tests that the state at the given path contains // the expected state string. func testStateOutput(t *testing.T, path string, expected string) { diff --git a/command/push.go b/command/push.go index cee61b7f6..98ad92d65 100644 --- a/command/push.go +++ b/command/push.go @@ -3,6 +3,7 @@ package command import ( "flag" "fmt" + "io" "os" "path/filepath" "strings" @@ -12,6 +13,11 @@ import ( type PushCommand struct { Meta + + // client is the client to use for the actual push operations. + // If this isn't set, then the Atlas client is used. This should + // really only be set for testing reasons (and is hence not exported). + client pushClient } func (c *PushCommand) Run(args []string) int { @@ -64,7 +70,7 @@ func (c *PushCommand) Run(args []string) int { } // Build the context based on the arguments given - _, planned, err := c.Context(contextOpts{ + ctx, planned, err := c.Context(contextOpts{ Path: configPath, StatePath: c.Meta.statePath, }) @@ -79,6 +85,13 @@ func (c *PushCommand) Run(args []string) int { return 1 } + // Ask for input + if err := ctx.Input(c.InputMode()); err != nil { + c.Ui.Error(fmt.Sprintf( + "Error while asking for variable input:\n\n%s", err)) + return 1 + } + // Build the archiving options, which includes everything it can // by default according to VCS rules but forcing the data directory. archiveOpts := &archive.ArchiveOpts{ @@ -92,7 +105,7 @@ func (c *PushCommand) Run(args []string) int { filepath.Join(c.DataDir(), "modules")) } - _, err = archive.CreateArchive(configPath, archiveOpts) + archiveR, err := archive.CreateArchive(configPath, archiveOpts) if err != nil { c.Ui.Error(fmt.Sprintf( "An error has occurred while archiving the module for uploading:\n"+ @@ -100,6 +113,13 @@ func (c *PushCommand) Run(args []string) int { return 1 } + // Upsert! + if err := c.client.Upsert(archiveR, archiveR.Size); err != nil { + c.Ui.Error(fmt.Sprintf( + "An error occurred while uploading the module:\n\n%s", err)) + return 1 + } + return 0 } @@ -126,3 +146,31 @@ Options: func (c *PushCommand) Synopsis() string { return "Upload this Terraform module to Atlas to run" } + +// pushClient is implementd internally to control where pushes go. This is +// either to Atlas or a mock for testing. +type pushClient interface { + Upsert(io.Reader, int64) error +} + +type mockPushClient struct { + File string + + UpsertCalled bool + UpsertError error +} + +func (c *mockPushClient) Upsert(data io.Reader, size int64) error { + f, err := os.Create(c.File) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.CopyN(f, data, size); err != nil { + return err + } + + c.UpsertCalled = true + return c.UpsertError +} diff --git a/command/push_test.go b/command/push_test.go index 8b52421b8..0cbe4cf23 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -1,12 +1,61 @@ package command import ( + "archive/tar" + "compress/gzip" + "io" + "os" + "reflect" + "sort" "testing" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/cli" ) +func TestPush_good(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + // Create remote state file, this should be pulled + conf, srv := testRemoteState(t, testState(), 200) + defer srv.Close() + + // Persist local remote state + s := terraform.NewState() + s.Serial = 5 + s.Remote = conf + testStateFileRemote(t, s) + + // Path where the archive will be "uploaded" to + archivePath := testTempFile(t) + defer os.Remove(archivePath) + + client := &mockPushClient{File: archivePath} + ui := new(cli.MockUi) + c := &PushCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + + client: client, + } + + args := []string{ + testFixturePath("push"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + actual := testArchiveStr(t, archivePath) + expected := []string{} + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } +} + func TestPush_noState(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) @@ -57,3 +106,70 @@ func TestPush_noRemoteState(t *testing.T) { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } } + +func TestPush_plan(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + // Create remote state file, this should be pulled + conf, srv := testRemoteState(t, testState(), 200) + defer srv.Close() + + // Persist local remote state + s := terraform.NewState() + s.Serial = 5 + s.Remote = conf + testStateFileRemote(t, s) + + // Create a plan + planPath := testPlanFile(t, &terraform.Plan{ + Module: testModule(t, "apply"), + }) + + ui := new(cli.MockUi) + c := &PushCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + } + + args := []string{planPath} + if code := c.Run(args); code != 1 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } +} + +func testArchiveStr(t *testing.T, path string) []string { + f, err := os.Open(path) + if err != nil { + t.Fatalf("err: %s", err) + } + defer f.Close() + + // Ungzip + gzipR, err := gzip.NewReader(f) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Accumulator + result := make([]string, 0, 10) + + // Untar + tarR := tar.NewReader(gzipR) + for { + header, err := tarR.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("err: %s", err) + } + + result = append(result, header.Name) + } + + sort.Strings(result) + return result +} diff --git a/command/test-fixtures/push/main.tf b/command/test-fixtures/push/main.tf new file mode 100644 index 000000000..919f140bb --- /dev/null +++ b/command/test-fixtures/push/main.tf @@ -0,0 +1 @@ +resource "aws_instance" "foo" {} diff --git a/terraform/context.go b/terraform/context.go index 62a06f431..fd59bd671 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -376,8 +376,6 @@ func (c *Context) Validate() ([]string, []error) { return walker.ValidationWarnings, rerrs.Errors } -<<<<<<< Updated upstream -======= // Variables will return the mapping of variables that were defined // for this Context. If Input was called, this mapping may be different // than what was given. @@ -390,7 +388,6 @@ func (c *Context) SetVariable(k, v string) { c.variables[k] = v } ->>>>>>> Stashed changes func (c *Context) acquireRun() chan<- struct{} { c.l.Lock() defer c.l.Unlock() From a1b424d53f60857ca7984e3e674fb40949830136 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Mar 2015 23:10:28 -0800 Subject: [PATCH 065/295] command/push: properly copy the data directory no matter what --- command/push.go | 6 ++++-- command/push_test.go | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/command/push.go b/command/push.go index 98ad92d65..4e39e4ab1 100644 --- a/command/push.go +++ b/command/push.go @@ -95,8 +95,10 @@ func (c *PushCommand) Run(args []string) int { // Build the archiving options, which includes everything it can // by default according to VCS rules but forcing the data directory. archiveOpts := &archive.ArchiveOpts{ - Include: []string{filepath.Join(c.DataDir())}, - VCS: true, + VCS: true, + Extra: map[string]string{ + DefaultDataDir: c.DataDir(), + }, } if !moduleLock { // If we're not locking modules, then exclude the modules dir. diff --git a/command/push_test.go b/command/push_test.go index 0cbe4cf23..e15402246 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -50,7 +50,11 @@ func TestPush_good(t *testing.T) { } actual := testArchiveStr(t, archivePath) - expected := []string{} + expected := []string{ + ".terraform/", + ".terraform/terraform.tfstate", + "main.tf", + } if !reflect.DeepEqual(actual, expected) { t.Fatalf("bad: %#v", actual) } From d37d9ea6ef0ef8ae58ed119558d87d429041fe3f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Mar 2015 00:06:54 -0800 Subject: [PATCH 066/295] command/push: send the context variables up --- command/push.go | 23 ++++++++++++++++++----- command/push_test.go | 5 +++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/command/push.go b/command/push.go index 4e39e4ab1..3430a9ab8 100644 --- a/command/push.go +++ b/command/push.go @@ -116,7 +116,11 @@ func (c *PushCommand) Run(args []string) int { } // Upsert! - if err := c.client.Upsert(archiveR, archiveR.Size); err != nil { + opts := &pushUpsertOptions{ + Archive: archiveR, + Variables: ctx.Variables(), + } + if err := c.client.Upsert(opts); err != nil { c.Ui.Error(fmt.Sprintf( "An error occurred while uploading the module:\n\n%s", err)) return 1 @@ -152,27 +156,36 @@ func (c *PushCommand) Synopsis() string { // pushClient is implementd internally to control where pushes go. This is // either to Atlas or a mock for testing. type pushClient interface { - Upsert(io.Reader, int64) error + Upsert(*pushUpsertOptions) error +} + +type pushUpsertOptions struct { + Archive *archive.Archive + Variables map[string]string } type mockPushClient struct { File string - UpsertCalled bool - UpsertError error + UpsertCalled bool + UpsertOptions *pushUpsertOptions + UpsertError error } -func (c *mockPushClient) Upsert(data io.Reader, size int64) error { +func (c *mockPushClient) Upsert(opts *pushUpsertOptions) error { f, err := os.Create(c.File) if err != nil { return err } defer f.Close() + data := opts.Archive + size := opts.Archive.Size if _, err := io.CopyN(f, data, size); err != nil { return err } c.UpsertCalled = true + c.UpsertOptions = opts return c.UpsertError } diff --git a/command/push_test.go b/command/push_test.go index e15402246..e6ee2dc18 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -58,6 +58,11 @@ func TestPush_good(t *testing.T) { if !reflect.DeepEqual(actual, expected) { t.Fatalf("bad: %#v", actual) } + + variables := make(map[string]string) + if !reflect.DeepEqual(client.UpsertOptions.Variables, variables) { + t.Fatalf("bad: %#v", client.UpsertOptions) + } } func TestPush_noState(t *testing.T) { From bf1414336990448f3f02e792861114b18e1a4cb4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Mar 2015 14:49:22 -0800 Subject: [PATCH 067/295] command/push: test that input is asked --- command/push.go | 3 +- command/push_test.go | 52 ++++++++++++++++++++++++ command/test-fixtures/push-input/main.tf | 3 ++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 command/test-fixtures/push-input/main.tf diff --git a/command/push.go b/command/push.go index 3430a9ab8..b88446040 100644 --- a/command/push.go +++ b/command/push.go @@ -1,7 +1,6 @@ package command import ( - "flag" "fmt" "io" "os" @@ -24,7 +23,7 @@ func (c *PushCommand) Run(args []string) int { var atlasToken string var moduleLock bool args = c.Meta.process(args, false) - cmdFlags := flag.NewFlagSet("push", flag.ContinueOnError) + cmdFlags := c.Meta.flagSet("push") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&atlasToken, "token", "", "") cmdFlags.BoolVar(&moduleLock, "module-lock", true, "") diff --git a/command/push_test.go b/command/push_test.go index e6ee2dc18..3c6415b62 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -2,6 +2,7 @@ package command import ( "archive/tar" + "bytes" "compress/gzip" "io" "os" @@ -65,6 +66,57 @@ func TestPush_good(t *testing.T) { } } +func TestPush_input(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + // Create remote state file, this should be pulled + conf, srv := testRemoteState(t, testState(), 200) + defer srv.Close() + + // Persist local remote state + s := terraform.NewState() + s.Serial = 5 + s.Remote = conf + testStateFileRemote(t, s) + + // Path where the archive will be "uploaded" to + archivePath := testTempFile(t) + defer os.Remove(archivePath) + + client := &mockPushClient{File: archivePath} + ui := new(cli.MockUi) + c := &PushCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + + client: client, + } + + // Disable test mode so input would be asked and setup the + // input reader/writers. + test = false + defer func() { test = true }() + defaultInputReader = bytes.NewBufferString("foo\n") + defaultInputWriter = new(bytes.Buffer) + + args := []string{ + testFixturePath("push-input"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + variables := map[string]string{ + "foo": "foo", + } + if !reflect.DeepEqual(client.UpsertOptions.Variables, variables) { + t.Fatalf("bad: %#v", client.UpsertOptions) + } +} + func TestPush_noState(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) diff --git a/command/test-fixtures/push-input/main.tf b/command/test-fixtures/push-input/main.tf new file mode 100644 index 000000000..55072f712 --- /dev/null +++ b/command/test-fixtures/push-input/main.tf @@ -0,0 +1,3 @@ +variable "foo" {} + +resource "test_instance" "foo" {} From eebd7b8aa3a0c50894af5bfa82666ec3a315d248 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Mar 2015 15:03:33 -0800 Subject: [PATCH 068/295] command/push: ask for only variables that are unset --- command/meta.go | 1 + command/push.go | 23 ++++++++ command/push_test.go | 55 +++++++++++++++++++ .../test-fixtures/push-input-partial/main.tf | 4 ++ 4 files changed, 83 insertions(+) create mode 100644 command/test-fixtures/push-input-partial/main.tf diff --git a/command/meta.go b/command/meta.go index 8a4578b5f..28a01c542 100644 --- a/command/meta.go +++ b/command/meta.go @@ -170,6 +170,7 @@ func (m *Meta) InputMode() terraform.InputMode { mode |= terraform.InputModeProvider if len(m.variables) == 0 && m.autoKey == "" { mode |= terraform.InputModeVar + mode |= terraform.InputModeVarUnset } return mode diff --git a/command/push.go b/command/push.go index b88446040..324e8a258 100644 --- a/command/push.go +++ b/command/push.go @@ -84,6 +84,17 @@ func (c *PushCommand) Run(args []string) int { return 1 } + // Get the variables we might already have + vars, err := c.client.Get("") + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error looking up prior pushed configuration: %s", err)) + return 1 + } + for k, v := range vars { + ctx.SetVariable(k, v) + } + // Ask for input if err := ctx.Input(c.InputMode()); err != nil { c.Ui.Error(fmt.Sprintf( @@ -155,6 +166,7 @@ func (c *PushCommand) Synopsis() string { // pushClient is implementd internally to control where pushes go. This is // either to Atlas or a mock for testing. type pushClient interface { + Get(string) (map[string]string, error) Upsert(*pushUpsertOptions) error } @@ -166,11 +178,22 @@ type pushUpsertOptions struct { type mockPushClient struct { File string + GetCalled bool + GetName string + GetResult map[string]string + GetError error + UpsertCalled bool UpsertOptions *pushUpsertOptions UpsertError error } +func (c *mockPushClient) Get(name string) (map[string]string, error) { + c.GetCalled = true + c.GetName = name + return c.GetResult, c.GetError +} + func (c *mockPushClient) Upsert(opts *pushUpsertOptions) error { f, err := os.Create(c.File) if err != nil { diff --git a/command/push_test.go b/command/push_test.go index 3c6415b62..68c5a8a42 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -117,6 +117,61 @@ func TestPush_input(t *testing.T) { } } +func TestPush_inputPartial(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + // Create remote state file, this should be pulled + conf, srv := testRemoteState(t, testState(), 200) + defer srv.Close() + + // Persist local remote state + s := terraform.NewState() + s.Serial = 5 + s.Remote = conf + testStateFileRemote(t, s) + + // Path where the archive will be "uploaded" to + archivePath := testTempFile(t) + defer os.Remove(archivePath) + + client := &mockPushClient{ + File: archivePath, + GetResult: map[string]string{"foo": "bar"}, + } + ui := new(cli.MockUi) + c := &PushCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + + client: client, + } + + // Disable test mode so input would be asked and setup the + // input reader/writers. + test = false + defer func() { test = true }() + defaultInputReader = bytes.NewBufferString("foo\n") + defaultInputWriter = new(bytes.Buffer) + + args := []string{ + testFixturePath("push-input-partial"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + variables := map[string]string{ + "foo": "bar", + "bar": "foo", + } + if !reflect.DeepEqual(client.UpsertOptions.Variables, variables) { + t.Fatalf("bad: %#v", client.UpsertOptions) + } +} + func TestPush_noState(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) diff --git a/command/test-fixtures/push-input-partial/main.tf b/command/test-fixtures/push-input-partial/main.tf new file mode 100644 index 000000000..8862603b0 --- /dev/null +++ b/command/test-fixtures/push-input-partial/main.tf @@ -0,0 +1,4 @@ +variable "foo" {} +variable "bar" {} + +resource "test_instance" "foo" {} From 9062bfda8918fbfaa870846225b4382a52b36bb3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Mar 2015 15:07:08 -0800 Subject: [PATCH 069/295] command/meta: fix test --- command/meta_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/meta_test.go b/command/meta_test.go index 4b1ae03f8..b0c4960f0 100644 --- a/command/meta_test.go +++ b/command/meta_test.go @@ -65,7 +65,7 @@ func TestMetaInputMode(t *testing.T) { t.Fatalf("err: %s", err) } - if m.InputMode() != terraform.InputModeStd { + if m.InputMode() != terraform.InputModeStd|terraform.InputModeVarUnset { t.Fatalf("bad: %#v", m.InputMode()) } } From 5e27bfc04079cb33762e2002b8373b644b489e85 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Mar 2015 15:12:39 -0800 Subject: [PATCH 070/295] command/push: read name from the config --- command/push.go | 15 ++++++++++++++- command/push_test.go | 4 ++++ command/test-fixtures/push-input-partial/main.tf | 4 ++++ command/test-fixtures/push-input/main.tf | 4 ++++ command/test-fixtures/push/main.tf | 4 ++++ terraform/context.go | 5 +++++ 6 files changed, 35 insertions(+), 1 deletion(-) diff --git a/command/push.go b/command/push.go index 324e8a258..75434a0d4 100644 --- a/command/push.go +++ b/command/push.go @@ -84,8 +84,19 @@ func (c *PushCommand) Run(args []string) int { return 1 } + // Get the configuration + config := ctx.Module().Config() + if config.Atlas == nil || config.Atlas.Name == "" { + c.Ui.Error( + "The name of this Terraform configuration in Atlas must be\n" + + "specified within your configuration or the command-line. To\n" + + "set it on the command-line, use the `-name` parameter.") + return 1 + } + name := config.Atlas.Name + // Get the variables we might already have - vars, err := c.client.Get("") + vars, err := c.client.Get(name) if err != nil { c.Ui.Error(fmt.Sprintf( "Error looking up prior pushed configuration: %s", err)) @@ -127,6 +138,7 @@ func (c *PushCommand) Run(args []string) int { // Upsert! opts := &pushUpsertOptions{ + Name: name, Archive: archiveR, Variables: ctx.Variables(), } @@ -171,6 +183,7 @@ type pushClient interface { } type pushUpsertOptions struct { + Name string Archive *archive.Archive Variables map[string]string } diff --git a/command/push_test.go b/command/push_test.go index 68c5a8a42..de91a759f 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -64,6 +64,10 @@ func TestPush_good(t *testing.T) { if !reflect.DeepEqual(client.UpsertOptions.Variables, variables) { t.Fatalf("bad: %#v", client.UpsertOptions) } + + if client.UpsertOptions.Name != "foo" { + t.Fatalf("bad: %#v", client.UpsertOptions) + } } func TestPush_input(t *testing.T) { diff --git a/command/test-fixtures/push-input-partial/main.tf b/command/test-fixtures/push-input-partial/main.tf index 8862603b0..8285c1ada 100644 --- a/command/test-fixtures/push-input-partial/main.tf +++ b/command/test-fixtures/push-input-partial/main.tf @@ -2,3 +2,7 @@ variable "foo" {} variable "bar" {} resource "test_instance" "foo" {} + +atlas { + name = "foo" +} diff --git a/command/test-fixtures/push-input/main.tf b/command/test-fixtures/push-input/main.tf index 55072f712..3bd930cf3 100644 --- a/command/test-fixtures/push-input/main.tf +++ b/command/test-fixtures/push-input/main.tf @@ -1,3 +1,7 @@ variable "foo" {} resource "test_instance" "foo" {} + +atlas { + name = "foo" +} diff --git a/command/test-fixtures/push/main.tf b/command/test-fixtures/push/main.tf index 919f140bb..265162636 100644 --- a/command/test-fixtures/push/main.tf +++ b/command/test-fixtures/push/main.tf @@ -1 +1,5 @@ resource "aws_instance" "foo" {} + +atlas { + name = "foo" +} diff --git a/terraform/context.go b/terraform/context.go index fd59bd671..86a804548 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -376,6 +376,11 @@ func (c *Context) Validate() ([]string, []error) { return walker.ValidationWarnings, rerrs.Errors } +// Module returns the module tree associated with this context. +func (c *Context) Module() *module.Tree { + return c.module +} + // Variables will return the mapping of variables that were defined // for this Context. If Input was called, this mapping may be different // than what was given. From da46e16f4f82fa9f6e760f758d9ee4373245fa0e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Mar 2015 15:14:41 -0800 Subject: [PATCH 071/295] command/push: can set the name on the CLI --- command/push.go | 22 +++++++++++++++------- command/push_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/command/push.go b/command/push.go index 75434a0d4..bb5efc5b7 100644 --- a/command/push.go +++ b/command/push.go @@ -22,11 +22,13 @@ type PushCommand struct { func (c *PushCommand) Run(args []string) int { var atlasToken string var moduleLock bool + var name string args = c.Meta.process(args, false) cmdFlags := c.Meta.flagSet("push") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&atlasToken, "token", "", "") cmdFlags.BoolVar(&moduleLock, "module-lock", true, "") + cmdFlags.StringVar(&name, "name", "", "") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -86,14 +88,16 @@ func (c *PushCommand) Run(args []string) int { // Get the configuration config := ctx.Module().Config() - if config.Atlas == nil || config.Atlas.Name == "" { - c.Ui.Error( - "The name of this Terraform configuration in Atlas must be\n" + - "specified within your configuration or the command-line. To\n" + - "set it on the command-line, use the `-name` parameter.") - return 1 + if name == "" { + if config.Atlas == nil || config.Atlas.Name == "" { + c.Ui.Error( + "The name of this Terraform configuration in Atlas must be\n" + + "specified within your configuration or the command-line. To\n" + + "set it on the command-line, use the `-name` parameter.") + return 1 + } + name = config.Atlas.Name } - name := config.Atlas.Name // Get the variables we might already have vars, err := c.client.Get(name) @@ -164,6 +168,10 @@ Options: their current checkout and uploaded completely. This prevents Atlas from running "terraform get". + -name= Name of the configuration in Atlas. This can also + be set in the configuration itself. Format is + typically: "username/name". + -token= Access token to use to upload. If blank, the ATLAS_TOKEN environmental variable will be used. diff --git a/command/push_test.go b/command/push_test.go index de91a759f..edd92897e 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -176,6 +176,48 @@ func TestPush_inputPartial(t *testing.T) { } } +func TestPush_name(t *testing.T) { + tmp, cwd := testCwd(t) + defer testFixCwd(t, tmp, cwd) + + // Create remote state file, this should be pulled + conf, srv := testRemoteState(t, testState(), 200) + defer srv.Close() + + // Persist local remote state + s := terraform.NewState() + s.Serial = 5 + s.Remote = conf + testStateFileRemote(t, s) + + // Path where the archive will be "uploaded" to + archivePath := testTempFile(t) + defer os.Remove(archivePath) + + client := &mockPushClient{File: archivePath} + ui := new(cli.MockUi) + c := &PushCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + + client: client, + } + + args := []string{ + "-name", "bar", + testFixturePath("push"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + if client.UpsertOptions.Name != "bar" { + t.Fatalf("bad: %#v", client.UpsertOptions) + } +} + func TestPush_noState(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) From ee1ad49829cb5eea69acfa17b4a99f8d295e06e4 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 6 Mar 2015 15:15:59 -0800 Subject: [PATCH 072/295] command/push: rename -module-lock to -module-upload --- command/push.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/command/push.go b/command/push.go index bb5efc5b7..6194ce936 100644 --- a/command/push.go +++ b/command/push.go @@ -21,13 +21,13 @@ type PushCommand struct { func (c *PushCommand) Run(args []string) int { var atlasToken string - var moduleLock bool + var moduleUpload bool var name string args = c.Meta.process(args, false) cmdFlags := c.Meta.flagSet("push") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&atlasToken, "token", "", "") - cmdFlags.BoolVar(&moduleLock, "module-lock", true, "") + cmdFlags.BoolVar(&moduleUpload, "module-upload", true, "") cmdFlags.StringVar(&name, "name", "", "") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { @@ -125,8 +125,8 @@ func (c *PushCommand) Run(args []string) int { DefaultDataDir: c.DataDir(), }, } - if !moduleLock { - // If we're not locking modules, then exclude the modules dir. + if !moduleUpload { + // If we're not uploading modules, then exclude the modules dir. archiveOpts.Exclude = append( archiveOpts.Exclude, filepath.Join(c.DataDir(), "modules")) @@ -164,7 +164,7 @@ Usage: terraform push [options] [DIR] Options: - -module-lock=true If true (default), then the modules are locked at + -module-upload=true If true (default), then the modules are locked at their current checkout and uploaded completely. This prevents Atlas from running "terraform get". From f857363aac67534640e077c7c739804cea574b32 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 9 Mar 2015 11:06:25 -0700 Subject: [PATCH 073/295] website: document push --- .../source/docs/commands/push.html.markdown | 53 +++++++++++++++++ .../source/docs/configuration/atlas.html.md | 58 +++++++++++++++++++ website/source/layouts/docs.erb | 8 +++ 3 files changed, 119 insertions(+) create mode 100644 website/source/docs/commands/push.html.markdown create mode 100644 website/source/docs/configuration/atlas.html.md diff --git a/website/source/docs/commands/push.html.markdown b/website/source/docs/commands/push.html.markdown new file mode 100644 index 000000000..5235b5754 --- /dev/null +++ b/website/source/docs/commands/push.html.markdown @@ -0,0 +1,53 @@ +--- +layout: "docs" +page_title: "Command: push" +sidebar_current: "docs-commands-push" +description: |- + The `terraform push` command is used to upload the Terraform configuration to HashiCorp's Atlas service for automatically managing your infrastructure in the cloud. +--- + +# Command: push + +The `terraform push` command uploads your Terraform configuration to +be managed by HashiCorp's [Atlas](https://atlas.hashicorp.com). +By uploading your configuration to Atlas, Atlas can automatically run +Terraform for you, will save all state transitions, will save plans, +and will keep a history of all Terraform runs. + +This makes it significantly easier to use Terraform as a team: team +members modify the Terraform configurations locally and continue to +use normal version control. When the Terraform configurations are ready +to be run, they are pushed to Atlas, and any member of your team can +run Terraform with the push of a button. + +Atlas can also be used to set ACLs on who can run Terraform, and a +future update of Atlas will allow parallel Terraform runs and automatically +perform infrastructure locking so only one run is modifying the same +infrastructure at a time. + +## Usage + +Usage: `terraform push [options] [path]` + +The `path` argument is the same as for the +[apply](/docs/commands/apply.html) command. + +The command-line flags are all optional. The list of available flags are: + +* `-module-upload=true` - If true (default), then the + [modules](/docs/modules/index.html) + being used are all locked at their current checkout and uploaded + completely to Atlas. This prevents Atlas from running `terraform get` + for you. + +* `-name=` - Name of the infrastructure configuration in Atlas. + The format of this is: "username/name" so that you can upload + configurations not just to your account but to other accounts and + organizations. This setting can also be set in the configuration + in the + [Atlas section](#). + +* `-no-color` - Disables output with coloring + +* `-token=` - Atlas API token to use to authorize the upload. + If blank, the `ATLAS_TOKEN` environmental variable will be used. diff --git a/website/source/docs/configuration/atlas.html.md b/website/source/docs/configuration/atlas.html.md new file mode 100644 index 000000000..e975c88ba --- /dev/null +++ b/website/source/docs/configuration/atlas.html.md @@ -0,0 +1,58 @@ +--- +layout: "docs" +page_title: "Configuring Atlas" +sidebar_current: "docs-config-atlas" +description: |- + Atlas is the ideal way to use Terraform in a team environment. Atlas will run Terraform for you, safely handle parallelization across different team members, save run history along with plans, and more. +--- + +# Atlas Configuration + +Terraform can be configured to be able to upload to HashiCorp's +[Atlas](https://atlas.hashicorp.com). This configuration doesn't change +the behavior of Terraform itself, it only configures your Terraform +configuration to support being uploaded to Atlas via the +[push command](/docs/commands/push.html). + +For more information on the benefits of uploading your Terraform +configuration to Atlas, please see the +[push command documentation](/docs/commands/push.html). + +This page assumes you're familiar with the +[configuration syntax](/docs/configuration/syntax.html) +already. + +## Example + +Atlas configuration looks like the following: + +``` +atlas { + name = "mitchellh/production-example" +} +``` + +## Description + +The `atlas` block configures the settings when Terraform is +[pushed](/docs/commands/push.html) to Atlas. Only one `atlas` block +is allowed. + +Within the block (the `{ }`) is configuration for Atlas uploading. +No keys are required, but the key typically set is `name`. + +**No value within the `atlas` block can use interpolations.** Due +to the nature of this configuration, interpolations are not possible. +If you want to parameterize these settings, use the Atlas block to +set defaults, then use the command-line flags of the +[push command](/docs/commands/push.html) to override. + +## Syntax + +The full syntax is: + +``` +atlas { + name = VALUE +} +``` diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index fa8bce84a..a0b31127a 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -45,6 +45,10 @@ Modules + > + Atlas + + @@ -79,6 +83,10 @@ plan + > + push + + > refresh From 1c0ffbd7bf5dbeafb48aca90ecea05262d9b7a7f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 9 Mar 2015 11:09:50 -0700 Subject: [PATCH 074/295] command/push: output the name/version of the pushed configuration --- command/push.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/command/push.go b/command/push.go index 6194ce936..5aa918a0d 100644 --- a/command/push.go +++ b/command/push.go @@ -146,12 +146,16 @@ func (c *PushCommand) Run(args []string) int { Archive: archiveR, Variables: ctx.Variables(), } - if err := c.client.Upsert(opts); err != nil { + vsn, err := c.client.Upsert(opts) + if err != nil { c.Ui.Error(fmt.Sprintf( "An error occurred while uploading the module:\n\n%s", err)) return 1 } + c.Ui.Output(c.Colorize().Color(fmt.Sprintf( + "[reset][bold][green]Configuration %s uploaded! (v%d)", + name, vsn))) return 0 } @@ -187,7 +191,7 @@ func (c *PushCommand) Synopsis() string { // either to Atlas or a mock for testing. type pushClient interface { Get(string) (map[string]string, error) - Upsert(*pushUpsertOptions) error + Upsert(*pushUpsertOptions) (int, error) } type pushUpsertOptions struct { @@ -206,6 +210,7 @@ type mockPushClient struct { UpsertCalled bool UpsertOptions *pushUpsertOptions + UpsertVersion int UpsertError error } @@ -215,20 +220,20 @@ func (c *mockPushClient) Get(name string) (map[string]string, error) { return c.GetResult, c.GetError } -func (c *mockPushClient) Upsert(opts *pushUpsertOptions) error { +func (c *mockPushClient) Upsert(opts *pushUpsertOptions) (int, error) { f, err := os.Create(c.File) if err != nil { - return err + return 0, err } defer f.Close() data := opts.Archive size := opts.Archive.Size if _, err := io.CopyN(f, data, size); err != nil { - return err + return 0, err } c.UpsertCalled = true c.UpsertOptions = opts - return c.UpsertError + return c.UpsertVersion, c.UpsertError } From 19c1771438e11f7ed8b6541efbc6afacb3f23160 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Mar 2015 13:42:48 -0700 Subject: [PATCH 075/295] command/push: integrate atlas push client --- command/push.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/command/push.go b/command/push.go index 5aa918a0d..ef5d3adfa 100644 --- a/command/push.go +++ b/command/push.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/hashicorp/atlas-go/archive" + "github.com/hashicorp/atlas-go/v1" ) type PushCommand struct { @@ -99,6 +100,21 @@ func (c *PushCommand) Run(args []string) int { name = config.Atlas.Name } + // Initialize the client if it isn't given. + if c.client == nil { + // Make sure to nil out our client so our token isn't sitting around + defer func() { c.client = nil }() + + // Initialize it to the default client, we set custom settings later + client := atlas.DefaultClient() + + if atlasToken != "" { + client.Token = atlasToken + } + + c.client = &atlasPushClient{Client: client} + } + // Get the variables we might already have vars, err := c.client.Get(name) if err != nil { @@ -200,6 +216,43 @@ type pushUpsertOptions struct { Variables map[string]string } +type atlasPushClient struct { + Client *atlas.Client +} + +func (c *atlasPushClient) Get(name string) (map[string]string, error) { + user, name, err := atlas.ParseSlug(name) + if err != nil { + return nil, err + } + + version, err := c.Client.TerraformConfigLatest(user, name) + if err != nil { + return nil, err + } + + return version.Variables, nil +} + +func (c *atlasPushClient) Upsert(opts *pushUpsertOptions) (int, error) { + user, name, err := atlas.ParseSlug(opts.Name) + if err != nil { + return 0, err + } + + data := &atlas.TerraformConfigVersion{ + Variables: opts.Variables, + } + + version, err := c.Client.CreateTerraformConfigVersion( + user, name, data, opts.Archive, opts.Archive.Size) + if err != nil { + return 0, err + } + + return version, nil +} + type mockPushClient struct { File string From 87907e24bae952d9992253f49573e76628d837e9 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 24 Mar 2015 15:34:13 -0500 Subject: [PATCH 076/295] provider/aws: Introduce IAM connection --- builtin/providers/aws/config.go | 4 + .../providers/aws/resource_aws_db_instance.go | 55 +++++++++++ builtin/providers/aws/tagsRDS.go | 94 +++++++++++++++++++ builtin/providers/aws/tagsRDS_test.go | 85 +++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 builtin/providers/aws/tagsRDS.go create mode 100644 builtin/providers/aws/tagsRDS_test.go diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 672af6f07..7e84d3a27 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/aws-sdk-go/gen/autoscaling" "github.com/hashicorp/aws-sdk-go/gen/ec2" "github.com/hashicorp/aws-sdk-go/gen/elb" + "github.com/hashicorp/aws-sdk-go/gen/iam" "github.com/hashicorp/aws-sdk-go/gen/rds" "github.com/hashicorp/aws-sdk-go/gen/route53" "github.com/hashicorp/aws-sdk-go/gen/s3" @@ -30,6 +31,7 @@ type AWSClient struct { r53conn *route53.Route53 region string rdsconn *rds.RDS + iamconn *iam.IAM } // Client configures and returns a fully initailized AWSClient @@ -70,6 +72,8 @@ func (c *Config) Client() (interface{}, error) { client.r53conn = route53.New(creds, "us-east-1", nil) log.Println("[INFO] Initializing EC2 Connection") client.ec2conn = ec2.New(creds, c.Region, nil) + + client.iamconn = iam.New(creds, c.Region, nil) } if len(errs) > 0 { diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index e99744a0f..c445a630b 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -6,6 +6,7 @@ import ( "time" "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/iam" "github.com/hashicorp/aws-sdk-go/gen/rds" "github.com/hashicorp/terraform/helper/hashcode" @@ -17,6 +18,7 @@ func resourceAwsDbInstance() *schema.Resource { return &schema.Resource{ Create: resourceAwsDbInstanceCreate, Read: resourceAwsDbInstanceRead, + Update: resourceAwsDbInstanceUpdate, Delete: resourceAwsDbInstanceDelete, Schema: map[string]*schema.Schema{ @@ -185,12 +187,14 @@ func resourceAwsDbInstance() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "tags": tagsSchema(), }, } } func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).rdsconn + tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{})) opts := rds.CreateDBInstanceMessage{ AllocatedStorage: aws.Integer(d.Get("allocated_storage").(int)), DBInstanceClass: aws.String(d.Get("instance_class").(string)), @@ -201,6 +205,7 @@ func resourceAwsDbInstanceCreate(d *schema.ResourceData, meta interface{}) error Engine: aws.String(d.Get("engine").(string)), EngineVersion: aws.String(d.Get("engine_version").(string)), StorageEncrypted: aws.Boolean(d.Get("storage_encrypted").(bool)), + Tags: tags, } if attr, ok := d.GetOk("storage_type"); ok { @@ -328,6 +333,28 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { d.Set("status", *v.DBInstanceStatus) d.Set("storage_encrypted", *v.StorageEncrypted) + // list tags for resource + // set tags + conn := meta.(*AWSClient).rdsconn + arn, err := buildRDSARN(d, meta) + if err != nil { + log.Printf("[DEBUG] Error building ARN for DB Instance, not setting Tags for DB %s", *v.DBName) + } else { + resp, err := conn.ListTagsForResource(&rds.ListTagsForResourceMessage{ + ResourceName: aws.String(arn), + }) + + if err != nil { + log.Print("[DEBUG] Error retreiving tags for ARN: %s", arn) + } + + var dt []rds.Tag + if len(resp.TagList) > 0 { + dt = resp.TagList + } + d.Set("tags", tagsToMapRDS(dt)) + } + // Create an empty schema.Set to hold all vpc security group ids ids := &schema.Set{ F: func(v interface{}) int { @@ -390,6 +417,21 @@ func resourceAwsDbInstanceDelete(d *schema.ResourceData, meta interface{}) error return nil } +func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).rdsconn + + d.Partial(true) + if arn, err := buildRDSARN(d, meta); err != nil { + if err := setTagsRDS(conn, d, arn); err != nil { + return err + } else { + d.SetPartial("tags") + } + } + d.Partial(false) + return resourceAwsDbInstanceRead(d, meta) +} + func resourceAwsBbInstanceRetrieve( d *schema.ResourceData, meta interface{}) (*rds.DBInstance, error) { conn := meta.(*AWSClient).rdsconn @@ -439,3 +481,16 @@ func resourceAwsDbInstanceStateRefreshFunc( return v, *v.DBInstanceStatus, nil } } + +func buildRDSARN(d *schema.ResourceData, meta interface{}) (string, error) { + iamconn := meta.(*AWSClient).iamconn + region := meta.(*AWSClient).region + // An zero value GetUserRequest{} defers to the currently logged in user + resp, err := iamconn.GetUser(&iam.GetUserRequest{}) + if err != nil { + return "", err + } + user := resp.User + arn := fmt.Sprintf("arn:aws:rds:%s:%s:db:%s", region, *user.UserID, d.Id()) + return arn, nil +} diff --git a/builtin/providers/aws/tagsRDS.go b/builtin/providers/aws/tagsRDS.go new file mode 100644 index 000000000..0677c2320 --- /dev/null +++ b/builtin/providers/aws/tagsRDS.go @@ -0,0 +1,94 @@ +package aws + +import ( + "log" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/rds" + "github.com/hashicorp/terraform/helper/schema" +) + +// setTags is a helper to set the tags for a resource. It expects the +// tags field to be named "tags" +func setTagsRDS(conn *rds.RDS, d *schema.ResourceData, arn string) error { + if d.HasChange("tags") { + oraw, nraw := d.GetChange("tags") + o := oraw.(map[string]interface{}) + n := nraw.(map[string]interface{}) + create, remove := diffTagsRDS(tagsFromMapRDS(o), tagsFromMapRDS(n)) + + // Set tags + if len(remove) > 0 { + log.Printf("[DEBUG] Removing tags: %#v", remove) + k := make([]string, 0, len(remove)) + for i, t := range remove { + k[i] = *t.Key + } + err := conn.RemoveTagsFromResource(&rds.RemoveTagsFromResourceMessage{ + ResourceName: aws.String(d.Get("name").(string)), + TagKeys: k, + }) + if err != nil { + return err + } + } + if len(create) > 0 { + log.Printf("[DEBUG] Creating tags: %#v", create) + err := conn.AddTagsToResource(&rds.AddTagsToResourceMessage{ + ResourceName: aws.String(arn), + Tags: create, + }) + if err != nil { + return err + } + } + } + + return nil +} + +// diffTags takes our tags locally and the ones remotely and returns +// the set of tags that must be created, and the set of tags that must +// be destroyed. +func diffTagsRDS(oldTags, newTags []rds.Tag) ([]rds.Tag, []rds.Tag) { + // First, we're creating everything we have + create := make(map[string]interface{}) + for _, t := range newTags { + create[*t.Key] = *t.Value + } + + // Build the list of what to remove + var remove []rds.Tag + for _, t := range oldTags { + old, ok := create[*t.Key] + if !ok || old != *t.Value { + // Delete it! + remove = append(remove, t) + } + } + + return tagsFromMapRDS(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMapRDS(m map[string]interface{}) []rds.Tag { + result := make([]rds.Tag, 0, len(m)) + for k, v := range m { + result = append(result, rds.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + }) + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMapRDS(ts []rds.Tag) map[string]string { + result := make(map[string]string) + for _, t := range ts { + result[*t.Key] = *t.Value + } + + return result +} diff --git a/builtin/providers/aws/tagsRDS_test.go b/builtin/providers/aws/tagsRDS_test.go new file mode 100644 index 000000000..1d9da8357 --- /dev/null +++ b/builtin/providers/aws/tagsRDS_test.go @@ -0,0 +1,85 @@ +package aws + +import ( + "fmt" + "reflect" + "testing" + + "github.com/hashicorp/aws-sdk-go/gen/rds" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestDiffRDSTags(t *testing.T) { + cases := []struct { + Old, New map[string]interface{} + Create, Remove map[string]string + }{ + // Basic add/remove + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "bar": "baz", + }, + Create: map[string]string{ + "bar": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + + // Modify + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "foo": "baz", + }, + Create: map[string]string{ + "foo": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + } + + for i, tc := range cases { + c, r := diffTagsRDS(tagsFromMapRDS(tc.Old), tagsFromMapRDS(tc.New)) + cm := tagsToMapRDS(c) + rm := tagsToMapRDS(r) + if !reflect.DeepEqual(cm, tc.Create) { + t.Fatalf("%d: bad create: %#v", i, cm) + } + if !reflect.DeepEqual(rm, tc.Remove) { + t.Fatalf("%d: bad remove: %#v", i, rm) + } + } +} + +// testAccCheckTags can be used to check the tags on a resource. +func testAccCheckRDSTags( + ts *[]rds.Tag, key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + m := tagsToMapRDS(*ts) + v, ok := m[key] + if value != "" && !ok { + return fmt.Errorf("Missing tag: %s", key) + } else if value == "" && ok { + return fmt.Errorf("Extra tag: %s", key) + } + if value == "" { + return nil + } + + if v != value { + return fmt.Errorf("%s: bad value: %s", key, v) + } + + return nil + } +} From 86f92119e25d0593a8ed4e91c5a11e92d9f6ac5d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Mar 2015 13:59:13 -0700 Subject: [PATCH 077/295] command/push: no version on GET should be okay --- command/push.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/command/push.go b/command/push.go index ef5d3adfa..63b6d2d69 100644 --- a/command/push.go +++ b/command/push.go @@ -231,7 +231,12 @@ func (c *atlasPushClient) Get(name string) (map[string]string, error) { return nil, err } - return version.Variables, nil + var variables map[string]string + if version != nil { + variables = version.Variables + } + + return variables, nil } func (c *atlasPushClient) Upsert(opts *pushUpsertOptions) (int, error) { From 20a81a9d056ac8528a688d54bb3a566516aaaa56 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Mar 2015 17:00:24 -0700 Subject: [PATCH 078/295] website: update docs --- .../source/docs/commands/push.html.markdown | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/website/source/docs/commands/push.html.markdown b/website/source/docs/commands/push.html.markdown index 5235b5754..130067416 100644 --- a/website/source/docs/commands/push.html.markdown +++ b/website/source/docs/commands/push.html.markdown @@ -45,9 +45,44 @@ The command-line flags are all optional. The list of available flags are: configurations not just to your account but to other accounts and organizations. This setting can also be set in the configuration in the - [Atlas section](#). + [Atlas section](/docs/configuration/atlas.html). * `-no-color` - Disables output with coloring * `-token=` - Atlas API token to use to authorize the upload. If blank, the `ATLAS_TOKEN` environmental variable will be used. + +## Packaged Files + +The files that are uploaded and packaged with a `push` are all the +files in the `path` given as the parameter to the command, recursively. +By default (unless `-vcs=false` is specified), Terraform will automatically +detect when a VCS such as Git is being used, and in that case will only +upload the files that are comitted. Because of this built-in intelligence, +you don't have to worry about excluding folders such as ".git" or ".hg" usually. + +If Terraform doesn't detect a VCS, it will upload all files. + +The reason Terraform uploads all of these files is because Terraform +cannot know what is and isn't being used for provisioning, so it uploads +all the files to be safe. To exclude certain files, specify the `-exclude` +flag when pushing, or specify the `exclude` parameter in the +[Atlas configuration section](/docs/configuration/atlas.html). + +## Remote State Requirement + +`terraform push` requires that +[remote state](http://localhost:4567/docs/commands/remote-config.html) +is enabled. The reasoning for this is simple: `terraform push` sends your +configuration to be managed remotely. For it to keep the state in sync +and for you to be able to easily access that state, remote state must +be enabled instead of juggling local files. + +While `terraform push` sends your configuration to be managed by Atlas, +the remote state backend _does not_ have to be Atlas. It can be anything +as long as it is accessible by the public internet, since Atlas will need +to be able to communicate to it. + +**Warning:** The credentials for accessing the remote state will be +sent up to Atlas as well. Therefore, we recommend you use access keys +that are restricted if possible. From 395dd04861929cf90aae5d572d46e59600742af3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Mar 2015 17:03:59 -0700 Subject: [PATCH 079/295] command/push: accept -vcs --- command/push.go | 8 ++++++-- website/source/docs/commands/push.html.markdown | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/command/push.go b/command/push.go index 63b6d2d69..05e4de18f 100644 --- a/command/push.go +++ b/command/push.go @@ -22,7 +22,7 @@ type PushCommand struct { func (c *PushCommand) Run(args []string) int { var atlasToken string - var moduleUpload bool + var archiveVCS, moduleUpload bool var name string args = c.Meta.process(args, false) cmdFlags := c.Meta.flagSet("push") @@ -30,6 +30,7 @@ func (c *PushCommand) Run(args []string) int { cmdFlags.StringVar(&atlasToken, "token", "", "") cmdFlags.BoolVar(&moduleUpload, "module-upload", true, "") cmdFlags.StringVar(&name, "name", "", "") + cmdFlags.BoolVar(&archiveVCS, "vcs", true, "") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -136,7 +137,7 @@ func (c *PushCommand) Run(args []string) int { // Build the archiving options, which includes everything it can // by default according to VCS rules but forcing the data directory. archiveOpts := &archive.ArchiveOpts{ - VCS: true, + VCS: archiveVCS, Extra: map[string]string{ DefaultDataDir: c.DataDir(), }, @@ -195,6 +196,9 @@ Options: -token= Access token to use to upload. If blank, the ATLAS_TOKEN environmental variable will be used. + -vcs=true If true (default), push will upload only files + comitted to your VCS, if detected. + ` return strings.TrimSpace(helpText) } diff --git a/website/source/docs/commands/push.html.markdown b/website/source/docs/commands/push.html.markdown index 130067416..e35ca2453 100644 --- a/website/source/docs/commands/push.html.markdown +++ b/website/source/docs/commands/push.html.markdown @@ -52,6 +52,11 @@ The command-line flags are all optional. The list of available flags are: * `-token=` - Atlas API token to use to authorize the upload. If blank, the `ATLAS_TOKEN` environmental variable will be used. +* `-vcs=true` - If true (default), then Terraform will detect if a VCS + is in use, such as Git, and will only upload files that are comitted to + version control. If no version control system is detected, Terraform will + upload all files in `path` (parameter to the command). + ## Packaged Files The files that are uploaded and packaged with a `push` are all the From 0229852e489dcad0db14f11e3eb350aa32456b9d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Mar 2015 17:39:37 -0700 Subject: [PATCH 080/295] command/push: change wording on error message --- command/push.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/push.go b/command/push.go index 05e4de18f..acee1b353 100644 --- a/command/push.go +++ b/command/push.go @@ -120,7 +120,7 @@ func (c *PushCommand) Run(args []string) int { vars, err := c.client.Get(name) if err != nil { c.Ui.Error(fmt.Sprintf( - "Error looking up prior pushed configuration: %s", err)) + "Error looking up previously pushed configuration: %s", err)) return 1 } for k, v := range vars { From d7aeb0136082fed049f9a12c647d35d98e5135d9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Mar 2015 17:40:12 -0700 Subject: [PATCH 081/295] website: remove localhost link --- website/source/docs/commands/push.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/commands/push.html.markdown b/website/source/docs/commands/push.html.markdown index e35ca2453..10617d569 100644 --- a/website/source/docs/commands/push.html.markdown +++ b/website/source/docs/commands/push.html.markdown @@ -77,7 +77,7 @@ flag when pushing, or specify the `exclude` parameter in the ## Remote State Requirement `terraform push` requires that -[remote state](http://localhost:4567/docs/commands/remote-config.html) +[remote state](/docs/commands/remote-config.html) is enabled. The reasoning for this is simple: `terraform push` sends your configuration to be managed remotely. For it to keep the state in sync and for you to be able to easily access that state, remote state must From 8d47f26bb77867716d5abeef24b1aa80d4eef15c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Mar 2015 17:41:26 -0700 Subject: [PATCH 082/295] command/push: address PR comments --- command/push.go | 6 +++--- website/source/docs/commands/push.html.markdown | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/command/push.go b/command/push.go index acee1b353..731a245f8 100644 --- a/command/push.go +++ b/command/push.go @@ -171,7 +171,7 @@ func (c *PushCommand) Run(args []string) int { } c.Ui.Output(c.Colorize().Color(fmt.Sprintf( - "[reset][bold][green]Configuration %s uploaded! (v%d)", + "[reset][bold][green]Configuration %q uploaded! (v%d)", name, vsn))) return 0 } @@ -193,8 +193,8 @@ Options: be set in the configuration itself. Format is typically: "username/name". - -token= Access token to use to upload. If blank, the ATLAS_TOKEN - environmental variable will be used. + -token= Access token to use to upload. If blank or unspecified, + the ATLAS_TOKEN environmental variable will be used. -vcs=true If true (default), push will upload only files comitted to your VCS, if detected. diff --git a/website/source/docs/commands/push.html.markdown b/website/source/docs/commands/push.html.markdown index 10617d569..e944261c9 100644 --- a/website/source/docs/commands/push.html.markdown +++ b/website/source/docs/commands/push.html.markdown @@ -50,7 +50,8 @@ The command-line flags are all optional. The list of available flags are: * `-no-color` - Disables output with coloring * `-token=` - Atlas API token to use to authorize the upload. - If blank, the `ATLAS_TOKEN` environmental variable will be used. + If blank or unspecified, the `ATLAS_TOKEN` environmental variable + will be used. * `-vcs=true` - If true (default), then Terraform will detect if a VCS is in use, such as Git, and will only upload files that are comitted to From 51614b63657e0731ec42d81d221a4aec2052d5c2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Mar 2015 17:42:40 -0700 Subject: [PATCH 083/295] command/push: upload-modules --- command/push.go | 4 ++-- website/source/docs/commands/push.html.markdown | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/command/push.go b/command/push.go index 731a245f8..64bccc767 100644 --- a/command/push.go +++ b/command/push.go @@ -28,7 +28,7 @@ func (c *PushCommand) Run(args []string) int { cmdFlags := c.Meta.flagSet("push") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&atlasToken, "token", "", "") - cmdFlags.BoolVar(&moduleUpload, "module-upload", true, "") + cmdFlags.BoolVar(&moduleUpload, "upload-modules", true, "") cmdFlags.StringVar(&name, "name", "", "") cmdFlags.BoolVar(&archiveVCS, "vcs", true, "") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } @@ -185,7 +185,7 @@ Usage: terraform push [options] [DIR] Options: - -module-upload=true If true (default), then the modules are locked at + -upload-modules=true If true (default), then the modules are locked at their current checkout and uploaded completely. This prevents Atlas from running "terraform get". diff --git a/website/source/docs/commands/push.html.markdown b/website/source/docs/commands/push.html.markdown index e944261c9..69378600f 100644 --- a/website/source/docs/commands/push.html.markdown +++ b/website/source/docs/commands/push.html.markdown @@ -34,7 +34,7 @@ The `path` argument is the same as for the The command-line flags are all optional. The list of available flags are: -* `-module-upload=true` - If true (default), then the +* `-upload-modules=true` - If true (default), then the [modules](/docs/modules/index.html) being used are all locked at their current checkout and uploaded completely to Atlas. This prevents Atlas from running `terraform get` From 280635d2b0d976574ec7a8dac9ae6053f422e24a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 24 Mar 2015 17:45:19 -0700 Subject: [PATCH 084/295] command/push: add -atlas-address --- command/push.go | 13 ++++++++++++- website/source/docs/commands/push.html.markdown | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/command/push.go b/command/push.go index 64bccc767..1a12a7559 100644 --- a/command/push.go +++ b/command/push.go @@ -21,11 +21,12 @@ type PushCommand struct { } func (c *PushCommand) Run(args []string) int { - var atlasToken string + var atlasAddress, atlasToken string var archiveVCS, moduleUpload bool var name string args = c.Meta.process(args, false) cmdFlags := c.Meta.flagSet("push") + cmdFlags.StringVar(&atlasAddress, "atlas-address", "", "") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&atlasToken, "token", "", "") cmdFlags.BoolVar(&moduleUpload, "upload-modules", true, "") @@ -108,6 +109,13 @@ func (c *PushCommand) Run(args []string) int { // Initialize it to the default client, we set custom settings later client := atlas.DefaultClient() + if atlasAddress != "" { + client, err = atlas.NewClient(atlasAddress) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error initializing Atlas client: %s", err)) + return 1 + } + } if atlasToken != "" { client.Token = atlasToken @@ -185,6 +193,9 @@ Usage: terraform push [options] [DIR] Options: + -atlas-address= An alternate address to an Atlas instance. Defaults + to https://atlas.hashicorp.com + -upload-modules=true If true (default), then the modules are locked at their current checkout and uploaded completely. This prevents Atlas from running "terraform get". diff --git a/website/source/docs/commands/push.html.markdown b/website/source/docs/commands/push.html.markdown index 69378600f..1a752e657 100644 --- a/website/source/docs/commands/push.html.markdown +++ b/website/source/docs/commands/push.html.markdown @@ -34,6 +34,9 @@ The `path` argument is the same as for the The command-line flags are all optional. The list of available flags are: +* `-atlas-address=` - An alternate address to an Atlas instance. + Defaults to `https://atlas.hashicorp.com`. + * `-upload-modules=true` - If true (default), then the [modules](/docs/modules/index.html) being used are all locked at their current checkout and uploaded From 676f3c5bab72b1e7b4ac40d61cc9d2047d5405d8 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 25 Mar 2015 10:05:15 -0500 Subject: [PATCH 085/295] fix typo --- builtin/providers/aws/resource_aws_db_instance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index c445a630b..6e6e84f5b 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -421,7 +421,7 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error conn := meta.(*AWSClient).rdsconn d.Partial(true) - if arn, err := buildRDSARN(d, meta); err != nil { + if arn, err := buildRDSARN(d, meta); err == nil { if err := setTagsRDS(conn, d, arn); err != nil { return err } else { From 65ff5b327d71f1445a1e2a137667df2af6666e4b Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 25 Mar 2015 10:14:45 -0500 Subject: [PATCH 086/295] upgrade VPC Ids and DB Subnet to be optionally computed --- builtin/providers/aws/resource_aws_db_instance.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index 6e6e84f5b..211400169 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -140,6 +140,7 @@ func resourceAwsDbInstance() *schema.Resource { "vpc_security_group_ids": &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)) @@ -164,6 +165,7 @@ func resourceAwsDbInstance() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, + Computed: true, }, "parameter_group_name": &schema.Schema{ From 398f4564c4a3b6877a3134f29d4dbf4220168d77 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 25 Mar 2015 10:32:54 -0500 Subject: [PATCH 087/295] fix formatting --- builtin/providers/aws/resource_aws_db_instance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index 211400169..b690c9a1a 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -347,7 +347,7 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { }) if err != nil { - log.Print("[DEBUG] Error retreiving tags for ARN: %s", arn) + log.Printf("[DEBUG] Error retreiving tags for ARN: %s", arn) } var dt []rds.Tag From 89854b0af5fcfdea512a4017ceab4c9420da8ec3 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 25 Mar 2015 11:10:12 -0500 Subject: [PATCH 088/295] fix index out of range error --- builtin/providers/aws/tagsRDS.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builtin/providers/aws/tagsRDS.go b/builtin/providers/aws/tagsRDS.go index 0677c2320..8eb592427 100644 --- a/builtin/providers/aws/tagsRDS.go +++ b/builtin/providers/aws/tagsRDS.go @@ -20,12 +20,13 @@ func setTagsRDS(conn *rds.RDS, d *schema.ResourceData, arn string) error { // Set tags if len(remove) > 0 { log.Printf("[DEBUG] Removing tags: %#v", remove) - k := make([]string, 0, len(remove)) + k := make([]string, len(remove), len(remove)) for i, t := range remove { k[i] = *t.Key } + err := conn.RemoveTagsFromResource(&rds.RemoveTagsFromResourceMessage{ - ResourceName: aws.String(d.Get("name").(string)), + ResourceName: aws.String(arn), TagKeys: k, }) if err != nil { From cddb057f40d4302663719c6fc556209f4eda81c8 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 25 Mar 2015 15:15:32 -0500 Subject: [PATCH 089/295] providers/aws: fix DNS options on VPC One typo on a `d.Get` and reuse of the request object was making it sad. Now it is happy! fixes #1301 --- builtin/providers/aws/resource_aws_vpc.go | 19 ++++++----- .../providers/aws/resource_aws_vpc_test.go | 32 ++++++++++++++++++- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/builtin/providers/aws/resource_aws_vpc.go b/builtin/providers/aws/resource_aws_vpc.go index 0ef8aa570..c59cf1984 100644 --- a/builtin/providers/aws/resource_aws_vpc.go +++ b/builtin/providers/aws/resource_aws_vpc.go @@ -185,13 +185,13 @@ func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error { // Turn on partial mode d.Partial(true) vpcid := d.Id() - modifyOpts := &ec2.ModifyVPCAttributeRequest{ - VPCID: &vpcid, - } if d.HasChange("enable_dns_hostnames") { val := d.Get("enable_dns_hostnames").(bool) - modifyOpts.EnableDNSHostnames = &ec2.AttributeBooleanValue{ - Value: &val, + modifyOpts := &ec2.ModifyVPCAttributeRequest{ + VPCID: &vpcid, + EnableDNSHostnames: &ec2.AttributeBooleanValue{ + Value: &val, + }, } log.Printf( @@ -205,9 +205,12 @@ func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error { } if d.HasChange("enable_dns_support") { - val := d.Get("enable_dns_hostnames").(bool) - modifyOpts.EnableDNSSupport = &ec2.AttributeBooleanValue{ - Value: &val, + val := d.Get("enable_dns_support").(bool) + modifyOpts := &ec2.ModifyVPCAttributeRequest{ + VPCID: &vpcid, + EnableDNSSupport: &ec2.AttributeBooleanValue{ + Value: &val, + }, } log.Printf( diff --git a/builtin/providers/aws/resource_aws_vpc_test.go b/builtin/providers/aws/resource_aws_vpc_test.go index 092f47806..ae103cf0d 100644 --- a/builtin/providers/aws/resource_aws_vpc_test.go +++ b/builtin/providers/aws/resource_aws_vpc_test.go @@ -2,11 +2,12 @@ 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" - "testing" ) func TestAccVpc_basic(t *testing.T) { @@ -184,6 +185,26 @@ func testAccCheckVpcExists(n string, vpc *ec2.VPC) resource.TestCheckFunc { } } +// https://github.com/hashicorp/terraform/issues/1301 +func TestAccVpc_bothDnsOptionsSet(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccVpcConfig_BothDnsOptions, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "aws_vpc.bar", "enable_dns_hostnames", "true"), + resource.TestCheckResourceAttr( + "aws_vpc.bar", "enable_dns_support", "true"), + ), + }, + }, + }) +} + const testAccVpcConfig = ` resource "aws_vpc" "foo" { cidr_block = "10.1.0.0/16" @@ -223,3 +244,12 @@ resource "aws_vpc" "bar" { cidr_block = "10.2.0.0/16" } ` + +const testAccVpcConfig_BothDnsOptions = ` +resource "aws_vpc" "bar" { + cidr_block = "10.2.0.0/16" + + enable_dns_hostnames = true + enable_dns_support = true +} +` From 041cce36b47244eccee798002e28921ef6fc03ab Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Wed, 25 Mar 2015 22:48:44 +0100 Subject: [PATCH 090/295] Some small fixes, needed to work with bigger templates --- .../providers/cloudstack/resource_cloudstack_disk.go | 10 +++++----- .../resource_cloudstack_vpn_customer_gateway.go | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/builtin/providers/cloudstack/resource_cloudstack_disk.go b/builtin/providers/cloudstack/resource_cloudstack_disk.go index 88ddff59e..382cd2876 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_disk.go +++ b/builtin/providers/cloudstack/resource_cloudstack_disk.go @@ -84,7 +84,7 @@ func resourceCloudStackDiskCreate(d *schema.ResourceData, meta interface{}) erro if d.Get("size").(int) != 0 { // Set the volume size - p.SetSize(d.Get("size").(int)) + p.SetSize(int64(d.Get("size").(int))) } // Retrieve the zone UUID @@ -141,7 +141,7 @@ func resourceCloudStackDiskRead(d *schema.ResourceData, meta interface{}) error d.Set("name", v.Name) d.Set("attach", v.Attached != "") // If attached this will contain a timestamp when attached d.Set("disk_offering", v.Diskofferingname) - d.Set("size", v.Size/(1024*1024*1024)) // Needed to get GB's again + d.Set("size", int(v.Size/(1024*1024*1024))) // Needed to get GB's again d.Set("zone", v.Zonename) if v.Attached != "" { @@ -196,7 +196,7 @@ func resourceCloudStackDiskUpdate(d *schema.ResourceData, meta interface{}) erro if d.Get("size").(int) != 0 { // Set the size - p.SetSize(d.Get("size").(int)) + p.SetSize(int64(d.Get("size").(int))) } // Set the shrink bit @@ -367,7 +367,7 @@ func isAttached(cs *cloudstack.CloudStackClient, id string) (bool, error) { return v.Attached != "", nil } -func retrieveDeviceID(device string) int { +func retrieveDeviceID(device string) int64 { switch device { case "/dev/xvdb", "D:": return 1 @@ -402,7 +402,7 @@ func retrieveDeviceID(device string) int { } } -func retrieveDeviceName(device int, os string) string { +func retrieveDeviceName(device int64, os string) string { switch device { case 1: if os == "Windows" { diff --git a/builtin/providers/cloudstack/resource_cloudstack_vpn_customer_gateway.go b/builtin/providers/cloudstack/resource_cloudstack_vpn_customer_gateway.go index 90a03aeb0..f27e28d38 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_vpn_customer_gateway.go +++ b/builtin/providers/cloudstack/resource_cloudstack_vpn_customer_gateway.go @@ -87,11 +87,11 @@ func resourceCloudStackVPNCustomerGatewayCreate(d *schema.ResourceData, meta int } if esplifetime, ok := d.GetOk("esp_lifetime"); ok { - p.SetEsplifetime(esplifetime.(int)) + p.SetEsplifetime(int64(esplifetime.(int))) } if ikelifetime, ok := d.GetOk("ike_lifetime"); ok { - p.SetIkelifetime(ikelifetime.(int)) + p.SetIkelifetime(int64(ikelifetime.(int))) } // Create the new VPN Customer Gateway @@ -128,8 +128,8 @@ func resourceCloudStackVPNCustomerGatewayRead(d *schema.ResourceData, meta inter d.Set("ike_policy", v.Ikepolicy) d.Set("ipsec_psk", v.Ipsecpsk) d.Set("dpd", v.Dpd) - d.Set("esp_lifetime", v.Esplifetime) - d.Set("ike_lifetime", v.Ikelifetime) + d.Set("esp_lifetime", int(v.Esplifetime)) + d.Set("ike_lifetime", int(v.Ikelifetime)) return nil } @@ -154,11 +154,11 @@ func resourceCloudStackVPNCustomerGatewayUpdate(d *schema.ResourceData, meta int } if esplifetime, ok := d.GetOk("esp_lifetime"); ok { - p.SetEsplifetime(esplifetime.(int)) + p.SetEsplifetime(int64(esplifetime.(int))) } if ikelifetime, ok := d.GetOk("ike_lifetime"); ok { - p.SetIkelifetime(ikelifetime.(int)) + p.SetIkelifetime(int64(ikelifetime.(int))) } // Update the VPN Customer Gateway From 2796f839500fe1bdceedeede90c8eed0d46dc162 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Mar 2015 15:23:04 -0700 Subject: [PATCH 091/295] command/remote-config: make the error message nicer for remote init /cc @sethvargo - based on UX issues you ran into yesterday --- command/remote_config.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/command/remote_config.go b/command/remote_config.go index cb95c4b94..2f106f909 100644 --- a/command/remote_config.go +++ b/command/remote_config.go @@ -177,7 +177,12 @@ func (c *RemoteConfigCommand) validateRemoteConfig() error { conf := c.remoteConf _, err := remote.NewClient(conf.Type, conf.Config) if err != nil { - c.Ui.Error(fmt.Sprintf("%s", err)) + c.Ui.Error(fmt.Sprintf( + "%s\n\n"+ + "If the error message above mentions requiring or modifying configuration\n" + + "options, these are set using the `-backend-config` flag. Example:\n" + + '-backend-config="name=foo" to set the `name` configuration', + err)) } return err } From 431f5e67068d35f26dbd19bc5ac7205cbce9c2bd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Mar 2015 15:26:38 -0700 Subject: [PATCH 092/295] command/remote-config: syntax --- command/remote_config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/command/remote_config.go b/command/remote_config.go index 2f106f909..7c06ac19a 100644 --- a/command/remote_config.go +++ b/command/remote_config.go @@ -179,9 +179,9 @@ func (c *RemoteConfigCommand) validateRemoteConfig() error { if err != nil { c.Ui.Error(fmt.Sprintf( "%s\n\n"+ - "If the error message above mentions requiring or modifying configuration\n" + - "options, these are set using the `-backend-config` flag. Example:\n" + - '-backend-config="name=foo" to set the `name` configuration', + "If the error message above mentions requiring or modifying configuration\n"+ + "options, these are set using the `-backend-config` flag. Example:\n"+ + "-backend-config=\"name=foo\" to set the `name` configuration", err)) } return err From 0d4c7887c5c918abd1b6212fb0593676fbb89699 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Mar 2015 15:38:24 -0700 Subject: [PATCH 093/295] terraform: don't increment state if one is nil --- terraform/state.go | 9 +++++++++ terraform/state_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/terraform/state.go b/terraform/state.go index 42e9023ba..4ec772fd2 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -214,6 +214,15 @@ func (s *State) DeepCopy() *State { // IncrementSerialMaybe increments the serial number of this state // if it different from the other state. func (s *State) IncrementSerialMaybe(other *State) { + if s == nil { + return + } + if other == nil { + return + } + if s.Serial > other.Serial { + return + } if !s.Equal(other) { s.Serial++ } diff --git a/terraform/state_test.go b/terraform/state_test.go index 9dfbbbf04..91f9170c7 100644 --- a/terraform/state_test.go +++ b/terraform/state_test.go @@ -178,6 +178,40 @@ func TestStateEqual(t *testing.T) { } } +func TestStateIncrementSerialMaybe(t *testing.T) { + cases := map[string]struct { + S1, S2 *State + Serial int64 + }{ + "S2 is nil": { + &State{}, + nil, + 0, + }, + "S2 is identical": { + &State{}, + &State{}, + 0, + }, + "S2 is different": { + &State{}, + &State{ + Modules: []*ModuleState{ + &ModuleState{Path: rootModulePath}, + }, + }, + 1, + }, + } + + for name, tc := range cases { + tc.S1.IncrementSerialMaybe(tc.S2) + if tc.S1.Serial != tc.Serial { + t.Fatalf("Bad: %s\nGot: %d", name, tc.S1.Serial) + } + } +} + func TestResourceStateEqual(t *testing.T) { cases := []struct { Result bool From f68f285f721d062e18bddade510277c6d9997c55 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Mar 2015 15:39:33 -0700 Subject: [PATCH 094/295] terraform: test case for higher S1 serial --- terraform/state_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/terraform/state_test.go b/terraform/state_test.go index 91f9170c7..e222b1b72 100644 --- a/terraform/state_test.go +++ b/terraform/state_test.go @@ -202,6 +202,16 @@ func TestStateIncrementSerialMaybe(t *testing.T) { }, 1, }, + "S1 serial is higher": { + &State{Serial: 5}, + &State{ + Serial: 3, + Modules: []*ModuleState{ + &ModuleState{Path: rootModulePath}, + }, + }, + 5, + }, } for name, tc := range cases { From 9c6e2cdc2167abba570a1a87ab492ef73a41e628 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Mar 2015 15:40:53 -0700 Subject: [PATCH 095/295] terraform: make sure our serial is always higher --- terraform/state.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terraform/state.go b/terraform/state.go index 4ec772fd2..1a1a28bea 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -224,6 +224,10 @@ func (s *State) IncrementSerialMaybe(other *State) { return } if !s.Equal(other) { + if other.Serial > s.Serial { + s.Serial = other.Serial + } + s.Serial++ } } From a0839da71a2e4af3ca456fa5fcb2860a0f8200bf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Mar 2015 16:28:52 -0700 Subject: [PATCH 096/295] terraform: merge provider configs before validate [GH-1282] --- terraform/context_test.go | 3 ++ terraform/eval_provider.go | 25 ++++++++++--- terraform/eval_provider_test.go | 65 +++++++++++++++++++++++++++++++++ terraform/eval_validate.go | 11 +----- terraform/evaltree_provider.go | 10 +++-- 5 files changed, 96 insertions(+), 18 deletions(-) diff --git a/terraform/context_test.go b/terraform/context_test.go index c6dd83b94..abffbf5c3 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -2505,6 +2505,9 @@ func TestContext2Input_provider(t *testing.T) { actual = c.Config["foo"] return nil } + p.ValidateFn = func(c *ResourceConfig) ([]string, []error) { + return nil, c.CheckSet([]string{"foo"}) + } if err := ctx.Input(InputModeStd); err != nil { t.Fatalf("err: %s", err) diff --git a/terraform/eval_provider.go b/terraform/eval_provider.go index f648fe46f..f8d61b1f1 100644 --- a/terraform/eval_provider.go +++ b/terraform/eval_provider.go @@ -6,17 +6,18 @@ import ( "github.com/hashicorp/terraform/config" ) -// EvalConfigProvider is an EvalNode implementation that configures -// a provider that is already initialized and retrieved. -type EvalConfigProvider struct { +// EvalBuildProviderConfig outputs a *ResourceConfig that is properly +// merged with parents and inputs on top of what is configured in the file. +type EvalBuildProviderConfig struct { Provider string Config **ResourceConfig + Output **ResourceConfig } -func (n *EvalConfigProvider) Eval(ctx EvalContext) (interface{}, error) { +func (n *EvalBuildProviderConfig) Eval(ctx EvalContext) (interface{}, error) { cfg := *n.Config - // If we have a configuration set, then use that + // If we have a configuration set, then merge that in if input := ctx.ProviderInput(n.Provider); input != nil { rc, err := config.NewRawConfig(input) if err != nil { @@ -33,7 +34,19 @@ func (n *EvalConfigProvider) Eval(ctx EvalContext) (interface{}, error) { cfg = NewResourceConfig(merged) } - return nil, ctx.ConfigureProvider(n.Provider, cfg) + *n.Output = cfg + return nil, nil +} + +// EvalConfigProvider is an EvalNode implementation that configures +// a provider that is already initialized and retrieved. +type EvalConfigProvider struct { + Provider string + Config **ResourceConfig +} + +func (n *EvalConfigProvider) Eval(ctx EvalContext) (interface{}, error) { + return nil, ctx.ConfigureProvider(n.Provider, *n.Config) } // EvalInitProvider is an EvalNode implementation that initializes a provider diff --git a/terraform/eval_provider_test.go b/terraform/eval_provider_test.go index 849e434a6..5d50d746b 100644 --- a/terraform/eval_provider_test.go +++ b/terraform/eval_provider_test.go @@ -5,6 +5,71 @@ import ( "testing" ) +func TestEvalBuildProviderConfig_impl(t *testing.T) { + var _ EvalNode = new(EvalBuildProviderConfig) +} + +func TestEvalBuildProviderConfig(t *testing.T) { + config := testResourceConfig(t, map[string]interface{}{}) + provider := "foo" + + n := &EvalBuildProviderConfig{ + Provider: provider, + Config: &config, + Output: &config, + } + + ctx := &MockEvalContext{ + ParentProviderConfigConfig: testResourceConfig(t, map[string]interface{}{ + "foo": "bar", + }), + ProviderInputConfig: map[string]interface{}{ + "bar": "baz", + }, + } + if _, err := n.Eval(ctx); err != nil { + t.Fatalf("err: %s", err) + } + + expected := map[string]interface{}{ + "foo": "bar", + "bar": "baz", + } + if !reflect.DeepEqual(config.Raw, expected) { + t.Fatalf("bad: %#v", config.Raw) + } +} + +func TestEvalBuildProviderConfig_parentPriority(t *testing.T) { + config := testResourceConfig(t, map[string]interface{}{}) + provider := "foo" + + n := &EvalBuildProviderConfig{ + Provider: provider, + Config: &config, + Output: &config, + } + + ctx := &MockEvalContext{ + ParentProviderConfigConfig: testResourceConfig(t, map[string]interface{}{ + "foo": "bar", + }), + ProviderInputConfig: map[string]interface{}{ + "foo": "baz", + }, + } + if _, err := n.Eval(ctx); err != nil { + t.Fatalf("err: %s", err) + } + + expected := map[string]interface{}{ + "foo": "bar", + } + if !reflect.DeepEqual(config.Raw, expected) { + t.Fatalf("bad: %#v", config.Raw) + } +} + func TestEvalConfigProvider_impl(t *testing.T) { var _ EvalNode = new(EvalConfigProvider) } diff --git a/terraform/eval_validate.go b/terraform/eval_validate.go index c6c1f20ba..e808240a0 100644 --- a/terraform/eval_validate.go +++ b/terraform/eval_validate.go @@ -57,21 +57,14 @@ RETURN: // EvalValidateProvider is an EvalNode implementation that validates // the configuration of a resource. type EvalValidateProvider struct { - ProviderName string - Provider *ResourceProvider - Config **ResourceConfig + Provider *ResourceProvider + Config **ResourceConfig } func (n *EvalValidateProvider) Eval(ctx EvalContext) (interface{}, error) { provider := *n.Provider config := *n.Config - // Get the parent configuration if there is one - if parent := ctx.ParentProviderConfig(n.ProviderName); parent != nil { - merged := parent.raw.Merge(config.raw) - config = NewResourceConfig(merged) - } - warns, errs := provider.Validate(config) if len(warns) == 0 && len(errs) == 0 { return nil, nil diff --git a/terraform/evaltree_provider.go b/terraform/evaltree_provider.go index 89937d562..e2f51f06b 100644 --- a/terraform/evaltree_provider.go +++ b/terraform/evaltree_provider.go @@ -44,10 +44,14 @@ func ProviderEvalTree(n string, config *config.RawConfig) EvalNode { Config: config, Output: &resourceConfig, }, + &EvalBuildProviderConfig{ + Provider: n, + Config: &resourceConfig, + Output: &resourceConfig, + }, &EvalValidateProvider{ - ProviderName: n, - Provider: &provider, - Config: &resourceConfig, + Provider: &provider, + Config: &resourceConfig, }, &EvalConfigProvider{ Provider: n, From 27f4f5889b920c761314bd0b583193615ee7cbe2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Mar 2015 16:31:22 -0700 Subject: [PATCH 097/295] website: clarify docs on -var-file and terraform.tfvars [GH-1280] --- website/source/docs/commands/apply.html.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/source/docs/commands/apply.html.markdown b/website/source/docs/commands/apply.html.markdown index 8ae1e8ee9..c7e2c27da 100644 --- a/website/source/docs/commands/apply.html.markdown +++ b/website/source/docs/commands/apply.html.markdown @@ -49,5 +49,6 @@ The command-line flags are all optional. The list of available flags are: * `-var-file=foo` - Set variables in the Terraform configuration from a file. If "terraform.tfvars" is present, it will be automatically - loaded if this flag is not specified. + loaded first. Any files specified by `-var-file` override any values + in a "terraform.tfvars". From 83cb277583164d05d28707e18418569ebe37512e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 25 Mar 2015 16:35:27 -0700 Subject: [PATCH 098/295] command/output: don't panic if no root module in state [GH-1263] --- command/output.go | 2 +- command/output_test.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/command/output.go b/command/output.go index 05fd31f34..2e3c1bee0 100644 --- a/command/output.go +++ b/command/output.go @@ -39,7 +39,7 @@ func (c *OutputCommand) Run(args []string) int { } state := stateStore.State() - if len(state.RootModule().Outputs) == 0 { + if state.Empty() || len(state.RootModule().Outputs) == 0 { c.Ui.Error(fmt.Sprintf( "The state file has no outputs defined. Define an output\n" + "in your configuration with the `output` directive and re-run\n" + diff --git a/command/output_test.go b/command/output_test.go index fbcda9962..d3444c389 100644 --- a/command/output_test.go +++ b/command/output_test.go @@ -142,6 +142,27 @@ func TestOutput_noArgs(t *testing.T) { } } +func TestOutput_noState(t *testing.T) { + originalState := &terraform.State{} + statePath := testStateFile(t, originalState) + + ui := new(cli.MockUi) + c := &OutputCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "foo", + } + if code := c.Run(args); code != 1 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } +} + func TestOutput_noVars(t *testing.T) { originalState := &terraform.State{ Modules: []*terraform.ModuleState{ From fff05e2aadc6836fface159b2461445a4545b2f8 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 3 Mar 2015 22:36:25 +0000 Subject: [PATCH 099/295] Tags support added for AWS ASG --- builtin/providers/aws/autoscaling_tags.go | 170 ++++++++++++++++++ .../providers/aws/autoscaling_tags_test.go | 122 +++++++++++++ .../aws/resource_aws_autoscaling_group.go | 14 ++ .../resource_aws_autoscaling_group_test.go | 62 +++++++ 4 files changed, 368 insertions(+) create mode 100644 builtin/providers/aws/autoscaling_tags.go create mode 100644 builtin/providers/aws/autoscaling_tags_test.go diff --git a/builtin/providers/aws/autoscaling_tags.go b/builtin/providers/aws/autoscaling_tags.go new file mode 100644 index 000000000..508a0ddd8 --- /dev/null +++ b/builtin/providers/aws/autoscaling_tags.go @@ -0,0 +1,170 @@ +package aws + +import ( + "bytes" + "fmt" + "log" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/autoscaling" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +// tagsSchema returns the schema to use for tags. +func autoscalingTagsSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "value": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "propagate_at_launch": &schema.Schema{ + Type: schema.TypeBool, + Required: true, + }, + }, + }, + Set: autoscalingTagsToHash, + } +} + +func autoscalingTagsToHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["key"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["value"].(string))) + buf.WriteString(fmt.Sprintf("%t-", m["propagate_at_launch"].(bool))) + + return hashcode.String(buf.String()) +} + +// setTags is a helper to set the tags for a resource. It expects the +// tags field to be named "tag" +func setAutoscalingTags(conn *autoscaling.AutoScaling, d *schema.ResourceData) error { + if d.HasChange("tag") { + oraw, nraw := d.GetChange("tag") + o := setToMapByKey(oraw.(*schema.Set), "key") + n := setToMapByKey(nraw.(*schema.Set), "key") + + resourceID := d.Get("name").(string) + c, r := diffAutoscalingTags( + autoscalingTagsFromMap(o, resourceID), + autoscalingTagsFromMap(n, resourceID), + resourceID) + create := autoscaling.CreateOrUpdateTagsType{ + Tags: c, + } + remove := autoscaling.DeleteTagsType{ + Tags: r, + } + + // Set tags + if len(r) > 0 { + log.Printf("[DEBUG] Removing autoscaling tags: %#v", r) + if err := conn.DeleteTags(&remove); err != nil { + return err + } + } + if len(c) > 0 { + log.Printf("[DEBUG] Creating autoscaling tags: %#v", c) + if err := conn.CreateOrUpdateTags(&create); err != nil { + return err + } + } + } + + return nil +} + +// diffTags takes our tags locally and the ones remotely and returns +// the set of tags that must be created, and the set of tags that must +// be destroyed. +func diffAutoscalingTags(oldTags, newTags []autoscaling.Tag, resourceID string) ([]autoscaling.Tag, []autoscaling.Tag) { + // First, we're creating everything we have + create := make(map[string]interface{}) + for _, t := range newTags { + tag := map[string]interface{}{ + "value": *t.Value, + "propagate_at_launch": *t.PropagateAtLaunch, + } + create[*t.Key] = tag + } + + // Build the list of what to remove + var remove []autoscaling.Tag + for _, t := range oldTags { + old, ok := create[*t.Key].(map[string]interface{}) + + if !ok || old["value"] != *t.Value || old["propagate_at_launch"] != *t.PropagateAtLaunch { + // Delete it! + remove = append(remove, t) + } + } + + return autoscalingTagsFromMap(create, resourceID), remove +} + +// tagsFromMap returns the tags for the given map of data. +func autoscalingTagsFromMap(m map[string]interface{}, resourceID string) []autoscaling.Tag { + result := make([]autoscaling.Tag, 0, len(m)) + for k, v := range m { + attr := v.(map[string]interface{}) + result = append(result, autoscaling.Tag{ + Key: aws.String(k), + Value: aws.String(attr["value"].(string)), + PropagateAtLaunch: aws.Boolean(attr["propagate_at_launch"].(bool)), + ResourceID: aws.String(resourceID), + ResourceType: aws.String("auto-scaling-group"), + }) + } + + return result +} + +// autoscalingTagsToMap turns the list of tags into a map. +func autoscalingTagsToMap(ts []autoscaling.Tag) map[string]interface{} { + tags := make(map[string]interface{}) + for _, t := range ts { + tag := map[string]interface{}{ + "value": *t.Value, + "propagate_at_launch": *t.PropagateAtLaunch, + } + tags[*t.Key] = tag + } + + return tags +} + +// autoscalingTagDescriptionsToMap turns the list of tags into a map. +func autoscalingTagDescriptionsToMap(ts []autoscaling.TagDescription) map[string]map[string]interface{} { + tags := make(map[string]map[string]interface{}) + for _, t := range ts { + tag := map[string]interface{}{ + "value": t.Value, + "propagate_at_launch": t.PropagateAtLaunch, + } + tags[*t.Key] = tag + } + + return tags +} + +func setToMapByKey(s *schema.Set, key string) map[string]interface{} { + result := make(map[string]interface{}) + for _, rawData := range s.List() { + data := rawData.(map[string]interface{}) + result[data[key].(string)] = data + } + + return result +} diff --git a/builtin/providers/aws/autoscaling_tags_test.go b/builtin/providers/aws/autoscaling_tags_test.go new file mode 100644 index 000000000..7d61e3b18 --- /dev/null +++ b/builtin/providers/aws/autoscaling_tags_test.go @@ -0,0 +1,122 @@ +package aws + +import ( + "fmt" + "reflect" + "testing" + + "github.com/hashicorp/aws-sdk-go/gen/autoscaling" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestDiffAutoscalingTags(t *testing.T) { + cases := []struct { + Old, New map[string]interface{} + Create, Remove map[string]interface{} + }{ + // Basic add/remove + { + Old: map[string]interface{}{ + "Name": map[string]interface{}{ + "value": "bar", + "propagate_at_launch": true, + }, + }, + New: map[string]interface{}{ + "DifferentTag": map[string]interface{}{ + "value": "baz", + "propagate_at_launch": true, + }, + }, + Create: map[string]interface{}{ + "DifferentTag": map[string]interface{}{ + "value": "baz", + "propagate_at_launch": true, + }, + }, + Remove: map[string]interface{}{ + "Name": map[string]interface{}{ + "value": "bar", + "propagate_at_launch": true, + }, + }, + }, + + // Modify + { + Old: map[string]interface{}{ + "Name": map[string]interface{}{ + "value": "bar", + "propagate_at_launch": true, + }, + }, + New: map[string]interface{}{ + "Name": map[string]interface{}{ + "value": "baz", + "propagate_at_launch": false, + }, + }, + Create: map[string]interface{}{ + "Name": map[string]interface{}{ + "value": "baz", + "propagate_at_launch": false, + }, + }, + Remove: map[string]interface{}{ + "Name": map[string]interface{}{ + "value": "bar", + "propagate_at_launch": true, + }, + }, + }, + } + + var resourceID = "sample" + + for i, tc := range cases { + awsTagsOld := autoscalingTagsFromMap(tc.Old, resourceID) + awsTagsNew := autoscalingTagsFromMap(tc.New, resourceID) + + c, r := diffAutoscalingTags(awsTagsOld, awsTagsNew, resourceID) + + cm := autoscalingTagsToMap(c) + rm := autoscalingTagsToMap(r) + if !reflect.DeepEqual(cm, tc.Create) { + t.Fatalf("%d: bad create: \n%#v\n%#v", i, cm, tc.Create) + } + if !reflect.DeepEqual(rm, tc.Remove) { + t.Fatalf("%d: bad remove: \n%#v\n%#v", i, rm, tc.Remove) + } + } +} + +// testAccCheckTags can be used to check the tags on a resource. +func testAccCheckAutoscalingTags( + ts *[]autoscaling.TagDescription, key string, expected map[string]interface{}) resource.TestCheckFunc { + return func(s *terraform.State) error { + m := autoscalingTagDescriptionsToMap(*ts) + v, ok := m[key] + if !ok { + return fmt.Errorf("Missing tag: %s", key) + } + + if v["value"] != expected["value"].(string) || + v["propagate_at_launch"] != expected["propagate_at_launch"].(bool) { + return fmt.Errorf("%s: bad value: %s", key, v) + } + + return nil + } +} + +func testAccCheckAutoscalingTagNotExists(ts *[]autoscaling.TagDescription, key string) resource.TestCheckFunc { + return func(s *terraform.State) error { + m := autoscalingTagDescriptionsToMap(*ts) + if _, ok := m[key]; ok { + return fmt.Errorf("Tag exists when it should not: %s", key) + } + + return nil + } +} diff --git a/builtin/providers/aws/resource_aws_autoscaling_group.go b/builtin/providers/aws/resource_aws_autoscaling_group.go index 2950e252d..9e5dc60a5 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group.go @@ -118,6 +118,8 @@ func resourceAwsAutoscalingGroup() *schema.Resource { return hashcode.String(v.(string)) }, }, + + "tag": autoscalingTagsSchema(), }, } } @@ -133,6 +135,11 @@ func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) autoScalingGroupOpts.AvailabilityZones = expandStringList( d.Get("availability_zones").(*schema.Set).List()) + if v, ok := d.GetOk("tag"); ok { + autoScalingGroupOpts.Tags = autoscalingTagsFromMap( + setToMapByKey(v.(*schema.Set), "key"), d.Get("name").(string)) + } + if v, ok := d.GetOk("default_cooldown"); ok { autoScalingGroupOpts.DefaultCooldown = aws.Integer(v.(int)) } @@ -195,6 +202,7 @@ func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) e d.Set("min_size", *g.MinSize) d.Set("max_size", *g.MaxSize) d.Set("name", *g.AutoScalingGroupName) + d.Set("tag", g.Tags) d.Set("vpc_zone_identifier", strings.Split(*g.VPCZoneIdentifier, ",")) d.Set("termination_policies", g.TerminationPolicies) @@ -224,6 +232,12 @@ func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{}) opts.MaxSize = aws.Integer(d.Get("max_size").(int)) } + if err := setAutoscalingTags(autoscalingconn, d); err != nil { + return err + } else { + d.SetPartial("tag") + } + log.Printf("[DEBUG] AutoScaling Group update configuration: %#v", opts) err := autoscalingconn.UpdateAutoScalingGroup(&opts) if err != nil { diff --git a/builtin/providers/aws/resource_aws_autoscaling_group_test.go b/builtin/providers/aws/resource_aws_autoscaling_group_test.go index 2a7e053be..2763450f6 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group_test.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "reflect" "testing" "github.com/hashicorp/aws-sdk-go/aws" @@ -53,6 +54,42 @@ func TestAccAWSAutoScalingGroup_basic(t *testing.T) { resource.TestCheckResourceAttr( "aws_autoscaling_group.bar", "desired_capacity", "5"), testLaunchConfigurationName("aws_autoscaling_group.bar", &lc), + resource.TestCheckResourceAttr( + "aws_autoscaling_group.bar", "tag", "xxx"), + ), + }, + }, + }) +} + +func TestAccAWSAutoScalingGroup_tags(t *testing.T) { + var group autoscaling.AutoScalingGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSAutoScalingGroupConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), + testAccCheckAutoscalingTags(&group.Tags, "foo", map[string]interface{}{ + "value": "bar", + "propagate_at_launch": true, + }), + ), + }, + + resource.TestStep{ + Config: testAccCheckInstanceConfigTagsUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), + testAccCheckAutoscalingTagNotExists(&group.Tags, "foo"), + testAccCheckAutoscalingTags(&group.Tags, "bar", map[string]interface{}{ + "value": "baz", + "propagate_at_launch": true, + }), ), }, }, @@ -145,6 +182,19 @@ func testAccCheckAWSAutoScalingGroupAttributes(group *autoscaling.AutoScalingGro return fmt.Errorf("Bad launch configuration name: %s", *group.LaunchConfigurationName) } + t := autoscaling.Tag{ + Key: aws.String("Name"), + Value: aws.String("foo-bar"), + PropagateAtLaunch: aws.Boolean(true), + } + + if !reflect.DeepEqual(group.Tags[0], t) { + return fmt.Errorf( + "Got:\n\n%#v\n\nExpected:\n\n%#v\n", + group.Tags[0], + t) + } + return nil } } @@ -226,6 +276,12 @@ resource "aws_autoscaling_group" "bar" { termination_policies = ["OldestInstance"] launch_configuration = "${aws_launch_configuration.foobar.name}" + + tag { + key = "Name" + value = "foo-bar" + propagate_at_launch = true + } } ` @@ -253,6 +309,12 @@ resource "aws_autoscaling_group" "bar" { force_delete = true launch_configuration = "${aws_launch_configuration.new.name}" + + tag { + key = "Name" + value = "bar-foo" + propagate_at_launch = true + } } ` From 7950ace39940f2d92512e6729d1567307ab2a2dd Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 2 Mar 2015 23:40:47 +0000 Subject: [PATCH 100/295] Documentation for ASG Tags added --- .../providers/aws/r/autoscale.html.markdown | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/website/source/docs/providers/aws/r/autoscale.html.markdown b/website/source/docs/providers/aws/r/autoscale.html.markdown index bb592a819..fe0643b72 100644 --- a/website/source/docs/providers/aws/r/autoscale.html.markdown +++ b/website/source/docs/providers/aws/r/autoscale.html.markdown @@ -23,6 +23,17 @@ resource "aws_autoscaling_group" "bar" { desired_capacity = 4 force_delete = true launch_configuration = "${aws_launch_configuration.foobar.name}" + + tag { + key = "foo" + value = "bar" + propagate_at_launch = true + } + tag { + key = "lorem" + value = "ipsum" + propagate_at_launch = false + } } ``` @@ -44,6 +55,14 @@ The following arguments are supported: group names. * `vpc_zone_identifier` (Optional) A list of subnet IDs to launch resources in. * `termination_policies` (Optional) A list of policies to decide how the instances in the auto scale group should be terminated. +* `tag` (Optional) A list of tag blocks. Tags documented below. + +Tags support the following: + +* `key` - (Required) Key +* `value` - (Required) Value +* `propagate_at_launch` - (Required) Enables propagation of the tag to + Amazon EC2 instances launched via this ASG ## Attributes Reference From bd4aaac71a1607ec0b40096d44fab043e1a6a1bb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 09:11:32 -0700 Subject: [PATCH 101/295] config/module: failing unit test for GH-1232 --- .../module/test-fixtures/basic-parent/a/a.tf | 3 ++ .../module/test-fixtures/basic-parent/c/c.tf | 1 + .../module/test-fixtures/basic-parent/main.tf | 3 ++ config/module/tree_test.go | 38 +++++++++++++++++++ 4 files changed, 45 insertions(+) create mode 100644 config/module/test-fixtures/basic-parent/a/a.tf create mode 100644 config/module/test-fixtures/basic-parent/c/c.tf create mode 100644 config/module/test-fixtures/basic-parent/main.tf diff --git a/config/module/test-fixtures/basic-parent/a/a.tf b/config/module/test-fixtures/basic-parent/a/a.tf new file mode 100644 index 000000000..b9b44f464 --- /dev/null +++ b/config/module/test-fixtures/basic-parent/a/a.tf @@ -0,0 +1,3 @@ +module "b" { + source = "../c" +} diff --git a/config/module/test-fixtures/basic-parent/c/c.tf b/config/module/test-fixtures/basic-parent/c/c.tf new file mode 100644 index 000000000..fec56017d --- /dev/null +++ b/config/module/test-fixtures/basic-parent/c/c.tf @@ -0,0 +1 @@ +# Hello diff --git a/config/module/test-fixtures/basic-parent/main.tf b/config/module/test-fixtures/basic-parent/main.tf new file mode 100644 index 000000000..2326ee22a --- /dev/null +++ b/config/module/test-fixtures/basic-parent/main.tf @@ -0,0 +1,3 @@ +module "a" { + source = "./a" +} diff --git a/config/module/tree_test.go b/config/module/tree_test.go index d8a753522..2e4a82eec 100644 --- a/config/module/tree_test.go +++ b/config/module/tree_test.go @@ -94,6 +94,44 @@ func TestTreeLoad_duplicate(t *testing.T) { } } +func TestTreeLoad_parentRef(t *testing.T) { + storage := testStorage(t) + tree := NewTree("", testConfig(t, "basic-parent")) + + if tree.Loaded() { + t.Fatal("should not be loaded") + } + + // This should error because we haven't gotten things yet + if err := tree.Load(storage, GetModeNone); err == nil { + t.Fatal("should error") + } + + if tree.Loaded() { + t.Fatal("should not be loaded") + } + + // This should get things + if err := tree.Load(storage, GetModeGet); err != nil { + t.Fatalf("err: %s", err) + } + + if !tree.Loaded() { + t.Fatal("should be loaded") + } + + // This should no longer error + if err := tree.Load(storage, GetModeNone); err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(tree.String()) + expected := strings.TrimSpace(treeLoadStr) + if actual != expected { + t.Fatalf("bad: \n\n%s", actual) + } +} + func TestTreeLoad_subdir(t *testing.T) { storage := testStorage(t) tree := NewTree("", testConfig(t, "basic-subdir")) From c6d832333bd476f0cba4a382e84ec7d165471651 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 26 Mar 2015 09:17:27 -0500 Subject: [PATCH 102/295] provider/aws: Add tags to S3 --- .../providers/aws/resource_aws_s3_bucket.go | 17 ++- builtin/providers/aws/s3_tags.go | 112 ++++++++++++++++++ builtin/providers/aws/s3_tags_test.go | 85 +++++++++++++ 3 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/aws/s3_tags.go create mode 100644 builtin/providers/aws/s3_tags_test.go diff --git a/builtin/providers/aws/resource_aws_s3_bucket.go b/builtin/providers/aws/resource_aws_s3_bucket.go index d832190b0..0b5ef2326 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket.go +++ b/builtin/providers/aws/resource_aws_s3_bucket.go @@ -14,6 +14,7 @@ func resourceAwsS3Bucket() *schema.Resource { return &schema.Resource{ Create: resourceAwsS3BucketCreate, Read: resourceAwsS3BucketRead, + Update: resourceAwsS3BucketUpdate, Delete: resourceAwsS3BucketDelete, Schema: map[string]*schema.Schema{ @@ -29,6 +30,8 @@ func resourceAwsS3Bucket() *schema.Resource { Optional: true, ForceNew: true, }, + + "tags": tagsSchema(), }, } } @@ -64,7 +67,19 @@ func resourceAwsS3BucketCreate(d *schema.ResourceData, meta interface{}) error { // Assign the bucket name as the resource ID d.SetId(bucket) - return nil + return resourceAwsS3BucketUpdate(d, meta) +} + +func resourceAwsS3BucketUpdate(d *schema.ResourceData, meta interface{}) error { + s3conn := meta.(*AWSClient).s3conn + d.Partial(true) + if err := setTagsS3(s3conn, d); err != nil { + return err + } else { + d.SetPartial("tags") + } + d.Partial(false) + return resourceAwsS3BucketRead(d, meta) } func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error { diff --git a/builtin/providers/aws/s3_tags.go b/builtin/providers/aws/s3_tags.go new file mode 100644 index 000000000..43678952b --- /dev/null +++ b/builtin/providers/aws/s3_tags.go @@ -0,0 +1,112 @@ +package aws + +import ( + "crypto/md5" + "encoding/base64" + "encoding/xml" + "log" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/s3" + "github.com/hashicorp/terraform/helper/schema" +) + +// setTags is a helper to set the tags for a resource. It expects the +// tags field to be named "tags" +func setTagsS3(conn *s3.S3, d *schema.ResourceData) error { + if d.HasChange("tags") { + oraw, nraw := d.GetChange("tags") + o := oraw.(map[string]interface{}) + n := nraw.(map[string]interface{}) + create, remove := diffTagsS3(tagsFromMapS3(o), tagsFromMapS3(n)) + + // Set tags + if len(remove) > 0 { + log.Printf("[DEBUG] Removing tags: %#v", remove) + err := conn.DeleteBucketTagging(&s3.DeleteBucketTaggingRequest{ + Bucket: aws.String(d.Get("bucket").(string)), + }) + if err != nil { + return err + } + } + if len(create) > 0 { + log.Printf("[DEBUG] Creating tags: %#v", create) + tagging := s3.Tagging{ + TagSet: create, + XMLName: xml.Name{ + Space: "http://s3.amazonaws.com/doc/2006-03-01/", + Local: "Tagging", + }, + } + // AWS S3 API requires us to send a base64 encoded md5 hash of the + // content, which we need to build ourselves since aws-sdk-go does not. + b, err := xml.Marshal(tagging) + if err != nil { + return err + } + h := md5.New() + h.Write(b) + base := base64.StdEncoding.EncodeToString(h.Sum(nil)) + + req := &s3.PutBucketTaggingRequest{ + Bucket: aws.String(d.Get("bucket").(string)), + ContentMD5: aws.String(base), + Tagging: &tagging, + } + + err = conn.PutBucketTagging(req) + if err != nil { + return err + } + } + } + + return nil +} + +// diffTags takes our tags locally and the ones remotely and returns +// the set of tags that must be created, and the set of tags that must +// be destroyed. +func diffTagsS3(oldTags, newTags []s3.Tag) ([]s3.Tag, []s3.Tag) { + // First, we're creating everything we have + create := make(map[string]interface{}) + for _, t := range newTags { + create[*t.Key] = *t.Value + } + + // Build the list of what to remove + var remove []s3.Tag + for _, t := range oldTags { + old, ok := create[*t.Key] + if !ok || old != *t.Value { + // Delete it! + remove = append(remove, t) + } + } + + return tagsFromMapS3(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMapS3(m map[string]interface{}) []s3.Tag { + result := make([]s3.Tag, 0, len(m)) + for k, v := range m { + result = append(result, s3.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + }) + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMapS3(ts []s3.Tag) map[string]string { + result := make(map[string]string) + for _, t := range ts { + result[*t.Key] = *t.Value + } + + return result +} diff --git a/builtin/providers/aws/s3_tags_test.go b/builtin/providers/aws/s3_tags_test.go new file mode 100644 index 000000000..9b082c6e4 --- /dev/null +++ b/builtin/providers/aws/s3_tags_test.go @@ -0,0 +1,85 @@ +package aws + +import ( + "fmt" + "reflect" + "testing" + + "github.com/hashicorp/aws-sdk-go/gen/s3" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestDiffTagsS3(t *testing.T) { + cases := []struct { + Old, New map[string]interface{} + Create, Remove map[string]string + }{ + // Basic add/remove + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "bar": "baz", + }, + Create: map[string]string{ + "bar": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + + // Modify + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "foo": "baz", + }, + Create: map[string]string{ + "foo": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + } + + for i, tc := range cases { + c, r := diffTagsS3(tagsFromMapS3(tc.Old), tagsFromMapS3(tc.New)) + cm := tagsToMapS3(c) + rm := tagsToMapS3(r) + if !reflect.DeepEqual(cm, tc.Create) { + t.Fatalf("%d: bad create: %#v", i, cm) + } + if !reflect.DeepEqual(rm, tc.Remove) { + t.Fatalf("%d: bad remove: %#v", i, rm) + } + } +} + +// testAccCheckTags can be used to check the tags on a resource. +func testAccCheckTagsS3( + ts *[]s3.Tag, key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + m := tagsToMapS3(*ts) + v, ok := m[key] + if value != "" && !ok { + return fmt.Errorf("Missing tag: %s", key) + } else if value == "" && ok { + return fmt.Errorf("Extra tag: %s", key) + } + if value == "" { + return nil + } + + if v != value { + return fmt.Errorf("%s: bad value: %s", key, v) + } + + return nil + } +} From 2e11ca68df3e956dd7b23a924e4187cb481a25d8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 09:30:32 -0700 Subject: [PATCH 103/295] config/module: go back to the original folder when doing parent references --- config/module/detect_file.go | 20 ++++++++++++++++++++ config/module/tree_test.go | 7 ++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/config/module/detect_file.go b/config/module/detect_file.go index 2b8dbacbe..a7eb4c49f 100644 --- a/config/module/detect_file.go +++ b/config/module/detect_file.go @@ -2,6 +2,7 @@ package module import ( "fmt" + "os" "path/filepath" "runtime" ) @@ -20,7 +21,26 @@ func (d *FileDetector) Detect(src, pwd string) (string, bool, error) { "relative paths require a module with a pwd") } + // Stat the pwd to determine if its a symbolic link. If it is, + // then the pwd becomes the original directory. Otherwise, + // `filepath.Join` below does some weird stuff. + // + // We just ignore if the pwd doesn't exist. That error will be + // caught later when we try to use the URL. + if fi, err := os.Lstat(pwd); !os.IsNotExist(err) { + if err != nil { + return "", true, err + } + if fi.Mode()&os.ModeSymlink != 0 { + pwd, err = os.Readlink(pwd) + if err != nil { + return "", true, err + } + } + } + src = filepath.Join(pwd, src) + println(src) } return fmtFileURL(src), true, nil } diff --git a/config/module/tree_test.go b/config/module/tree_test.go index 2e4a82eec..9ac0c582d 100644 --- a/config/module/tree_test.go +++ b/config/module/tree_test.go @@ -126,7 +126,7 @@ func TestTreeLoad_parentRef(t *testing.T) { } actual := strings.TrimSpace(tree.String()) - expected := strings.TrimSpace(treeLoadStr) + expected := strings.TrimSpace(treeLoadParentStr) if actual != expected { t.Fatalf("bad: \n\n%s", actual) } @@ -277,6 +277,11 @@ root foo ` +const treeLoadParentStr = ` +root + a + b +` const treeLoadSubdirStr = ` root foo From 44fce5ce600027f251353e9925f3bde35709ba2c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 09:31:58 -0700 Subject: [PATCH 104/295] config/module: remove debug --- config/module/detect_file.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/module/detect_file.go b/config/module/detect_file.go index a7eb4c49f..859739f95 100644 --- a/config/module/detect_file.go +++ b/config/module/detect_file.go @@ -40,8 +40,8 @@ func (d *FileDetector) Detect(src, pwd string) (string, bool, error) { } src = filepath.Join(pwd, src) - println(src) } + return fmtFileURL(src), true, nil } From be60d39b24cd245bef76ce2857a6e11773efe864 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 26 Mar 2015 11:45:16 -0500 Subject: [PATCH 105/295] Add proper reading/updating of tags for S3 --- builtin/providers/aws/resource_aws_s3_bucket.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/builtin/providers/aws/resource_aws_s3_bucket.go b/builtin/providers/aws/resource_aws_s3_bucket.go index 0b5ef2326..c45e439b6 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket.go +++ b/builtin/providers/aws/resource_aws_s3_bucket.go @@ -91,6 +91,18 @@ func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error { if err != nil { return err } + + resp, err := s3conn.GetBucketTagging(&s3.GetBucketTaggingRequest{ + Bucket: aws.String(d.Id()), + }) + if err != nil { + return err + } + + if err := d.Set("tags", tagsToMapS3(resp.TagSet)); err != nil { + return err + } + return nil } From e5a375ae586e23c79de797f067e5e474b986cfbe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 09:44:51 -0700 Subject: [PATCH 106/295] providers/heroku: empty config vars block shouldn't panic [GH-1211] --- builtin/providers/heroku/resource_heroku_app.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/builtin/providers/heroku/resource_heroku_app.go b/builtin/providers/heroku/resource_heroku_app.go index af27c7b26..52954aa5d 100644 --- a/builtin/providers/heroku/resource_heroku_app.go +++ b/builtin/providers/heroku/resource_heroku_app.go @@ -358,14 +358,18 @@ func updateConfigVars( vars := make(map[string]*string) for _, v := range o { - for k, _ := range v.(map[string]interface{}) { - vars[k] = nil + if v != nil { + for k, _ := range v.(map[string]interface{}) { + vars[k] = nil + } } } for _, v := range n { - for k, v := range v.(map[string]interface{}) { - val := v.(string) - vars[k] = &val + if v != nil { + for k, v := range v.(map[string]interface{}) { + val := v.(string) + vars[k] = &val + } } } From 1b699aae7d25054116e20b4164f9355dd34bc2ea Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 26 Mar 2015 11:58:20 -0500 Subject: [PATCH 107/295] small code cleanup --- builtin/providers/aws/resource_aws_s3_bucket.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket.go b/builtin/providers/aws/resource_aws_s3_bucket.go index c45e439b6..cb32d5fa3 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket.go +++ b/builtin/providers/aws/resource_aws_s3_bucket.go @@ -72,13 +72,9 @@ func resourceAwsS3BucketCreate(d *schema.ResourceData, meta interface{}) error { func resourceAwsS3BucketUpdate(d *schema.ResourceData, meta interface{}) error { s3conn := meta.(*AWSClient).s3conn - d.Partial(true) if err := setTagsS3(s3conn, d); err != nil { return err - } else { - d.SetPartial("tags") } - d.Partial(false) return resourceAwsS3BucketRead(d, meta) } From 5e8535c5e0282d0dbe4368712d71ecb4b0859793 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 10:13:57 -0700 Subject: [PATCH 108/295] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6c694a37..733e2d10c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ IMPROVEMENTS: 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] + * providers/google: Add `size` option to disk blocks for instances. [GH-1284] BUG FIXES: From 3565ae034e1aec50a63d2abe7dcd2cb7dfe7579a Mon Sep 17 00:00:00 2001 From: Anton Tereshchenkov Date: Sun, 25 Jan 2015 12:30:09 +0800 Subject: [PATCH 109/295] providers/digitalocean: force fqdn in dns rr value Fixes a bug that forces DNS record to be recreated when dealing with records that have domain values (CNAME, MX, NS, etc.) --- .../resource_digitalocean_record.go | 10 +++- .../resource_digitalocean_record_test.go | 52 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/builtin/providers/digitalocean/resource_digitalocean_record.go b/builtin/providers/digitalocean/resource_digitalocean_record.go index d365e4706..1dcf7a3a9 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_record.go +++ b/builtin/providers/digitalocean/resource_digitalocean_record.go @@ -68,10 +68,18 @@ func resourceDigitalOceanRecord() *schema.Resource { func resourceDigitalOceanRecordCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*digitalocean.Client) + rrValue := d.Get("value").(string) + // Ensure all records with domain value are absolute (ending with dot) + if t := d.Get("type").(string); t == "CNAME" || t == "MX" || t == "NS" || t == "SRV" { + if rrValue[len(rrValue)-1] != '.' { + rrValue += "." + } + } + newRecord := digitalocean.CreateRecord{ Type: d.Get("type").(string), Name: d.Get("name").(string), - Data: d.Get("value").(string), + Data: rrValue, Priority: d.Get("priority").(string), Port: d.Get("port").(string), Weight: d.Get("weight").(string), diff --git a/builtin/providers/digitalocean/resource_digitalocean_record_test.go b/builtin/providers/digitalocean/resource_digitalocean_record_test.go index 66ac2bb5f..dd4b1010f 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_record_test.go +++ b/builtin/providers/digitalocean/resource_digitalocean_record_test.go @@ -76,6 +76,33 @@ func TestAccDigitalOceanRecord_Updated(t *testing.T) { }) } +func TestAccDigitalOceanRecord_HostnameValue(t *testing.T) { + var record digitalocean.Record + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDigitalOceanRecordDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckDigitalOceanRecordConfig_cname, + Check: resource.ComposeTestCheckFunc( + testAccCheckDigitalOceanRecordExists("digitalocean_record.foobar", &record), + testAccCheckDigitalOceanRecordAttributesHostname(&record), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "name", "terraform"), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "domain", "foobar-test-terraform.com"), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "value", "a.foobar-test-terraform.com"), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "type", "CNAME"), + ), + }, + }, + }) +} + func testAccCheckDigitalOceanRecordDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*digitalocean.Client) @@ -146,6 +173,17 @@ func testAccCheckDigitalOceanRecordExists(n string, record *digitalocean.Record) } } +func testAccCheckDigitalOceanRecordAttributesHostname(record *digitalocean.Record) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if record.Data != "a.foobar-test-terraform.com" { + return fmt.Errorf("Bad value: %s", record.Data) + } + + return nil + } +} + const testAccCheckDigitalOceanRecordConfig_basic = ` resource "digitalocean_domain" "foobar" { name = "foobar-test-terraform.com" @@ -173,3 +211,17 @@ resource "digitalocean_record" "foobar" { value = "192.168.0.11" type = "A" }` + +const testAccCheckDigitalOceanRecordConfig_cname = ` +resource "digitalocean_domain" "foobar" { + name = "foobar-test-terraform.com" + ip_address = "192.168.0.10" +} + +resource "digitalocean_record" "foobar" { + domain = "${digitalocean_domain.foobar.name}" + + name = "terraform" + value = "a.foobar-test-terraform.com" + type = "CNAME" +}` From 6f76340192147f7a1f29a2831d2ed5cb6f167b8b Mon Sep 17 00:00:00 2001 From: Anton Tereshchenkov Date: Sat, 28 Feb 2015 18:55:10 +0800 Subject: [PATCH 110/295] providers/digitalocean: add dot in GET response Added tests for relative and external CNAME values. --- .../resource_digitalocean_record.go | 25 +++-- .../resource_digitalocean_record_test.go | 94 +++++++++++++++++-- 2 files changed, 103 insertions(+), 16 deletions(-) diff --git a/builtin/providers/digitalocean/resource_digitalocean_record.go b/builtin/providers/digitalocean/resource_digitalocean_record.go index 1dcf7a3a9..78a4e8911 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_record.go +++ b/builtin/providers/digitalocean/resource_digitalocean_record.go @@ -68,18 +68,10 @@ func resourceDigitalOceanRecord() *schema.Resource { func resourceDigitalOceanRecordCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*digitalocean.Client) - rrValue := d.Get("value").(string) - // Ensure all records with domain value are absolute (ending with dot) - if t := d.Get("type").(string); t == "CNAME" || t == "MX" || t == "NS" || t == "SRV" { - if rrValue[len(rrValue)-1] != '.' { - rrValue += "." - } - } - newRecord := digitalocean.CreateRecord{ Type: d.Get("type").(string), Name: d.Get("name").(string), - Data: rrValue, + Data: d.Get("value").(string), Priority: d.Get("priority").(string), Port: d.Get("port").(string), Weight: d.Get("weight").(string), @@ -99,8 +91,9 @@ func resourceDigitalOceanRecordCreate(d *schema.ResourceData, meta interface{}) func resourceDigitalOceanRecordRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*digitalocean.Client) + domain := d.Get("domain").(string) - rec, err := client.RetrieveRecord(d.Get("domain").(string), d.Id()) + rec, err := client.RetrieveRecord(domain, d.Id()) if err != nil { // If the record is somehow already destroyed, mark as // succesfully gone @@ -112,6 +105,18 @@ func resourceDigitalOceanRecordRead(d *schema.ResourceData, meta interface{}) er return err } + // Update response data for records with domain value + if t := rec.Type; t == "CNAME" || t == "MX" || t == "NS" || t == "SRV" { + // Append dot to response if resource value is absolute + if value := d.Get("value").(string); strings.HasSuffix(value, ".") { + rec.Data += "." + // If resource value ends with current domain, make response data absolute + if strings.HasSuffix(value, domain+".") { + rec.Data += domain + "." + } + } + } + d.Set("name", rec.Name) d.Set("type", rec.Type) d.Set("value", rec.Data) diff --git a/builtin/providers/digitalocean/resource_digitalocean_record_test.go b/builtin/providers/digitalocean/resource_digitalocean_record_test.go index dd4b1010f..139fd30b7 100644 --- a/builtin/providers/digitalocean/resource_digitalocean_record_test.go +++ b/builtin/providers/digitalocean/resource_digitalocean_record_test.go @@ -88,13 +88,67 @@ func TestAccDigitalOceanRecord_HostnameValue(t *testing.T) { Config: testAccCheckDigitalOceanRecordConfig_cname, Check: resource.ComposeTestCheckFunc( testAccCheckDigitalOceanRecordExists("digitalocean_record.foobar", &record), - testAccCheckDigitalOceanRecordAttributesHostname(&record), + testAccCheckDigitalOceanRecordAttributesHostname("a", &record), resource.TestCheckResourceAttr( "digitalocean_record.foobar", "name", "terraform"), resource.TestCheckResourceAttr( "digitalocean_record.foobar", "domain", "foobar-test-terraform.com"), resource.TestCheckResourceAttr( - "digitalocean_record.foobar", "value", "a.foobar-test-terraform.com"), + "digitalocean_record.foobar", "value", "a.foobar-test-terraform.com."), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "type", "CNAME"), + ), + }, + }, + }) +} + +func TestAccDigitalOceanRecord_RelativeHostnameValue(t *testing.T) { + var record digitalocean.Record + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDigitalOceanRecordDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckDigitalOceanRecordConfig_relative_cname, + Check: resource.ComposeTestCheckFunc( + testAccCheckDigitalOceanRecordExists("digitalocean_record.foobar", &record), + testAccCheckDigitalOceanRecordAttributesHostname("a.b", &record), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "name", "terraform"), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "domain", "foobar-test-terraform.com"), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "value", "a.b"), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "type", "CNAME"), + ), + }, + }, + }) +} + +func TestAccDigitalOceanRecord_ExternalHostnameValue(t *testing.T) { + var record digitalocean.Record + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDigitalOceanRecordDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckDigitalOceanRecordConfig_external_cname, + Check: resource.ComposeTestCheckFunc( + testAccCheckDigitalOceanRecordExists("digitalocean_record.foobar", &record), + testAccCheckDigitalOceanRecordAttributesHostname("a.foobar-test-terraform.net", &record), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "name", "terraform"), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "domain", "foobar-test-terraform.com"), + resource.TestCheckResourceAttr( + "digitalocean_record.foobar", "value", "a.foobar-test-terraform.net."), resource.TestCheckResourceAttr( "digitalocean_record.foobar", "type", "CNAME"), ), @@ -173,11 +227,11 @@ func testAccCheckDigitalOceanRecordExists(n string, record *digitalocean.Record) } } -func testAccCheckDigitalOceanRecordAttributesHostname(record *digitalocean.Record) resource.TestCheckFunc { +func testAccCheckDigitalOceanRecordAttributesHostname(data string, record *digitalocean.Record) resource.TestCheckFunc { return func(s *terraform.State) error { - if record.Data != "a.foobar-test-terraform.com" { - return fmt.Errorf("Bad value: %s", record.Data) + if record.Data != data { + return fmt.Errorf("Bad value: expected %s, got %s", data, record.Data) } return nil @@ -222,6 +276,34 @@ resource "digitalocean_record" "foobar" { domain = "${digitalocean_domain.foobar.name}" name = "terraform" - value = "a.foobar-test-terraform.com" + value = "a.foobar-test-terraform.com." + type = "CNAME" +}` + +const testAccCheckDigitalOceanRecordConfig_relative_cname = ` +resource "digitalocean_domain" "foobar" { + name = "foobar-test-terraform.com" + ip_address = "192.168.0.10" +} + +resource "digitalocean_record" "foobar" { + domain = "${digitalocean_domain.foobar.name}" + + name = "terraform" + value = "a.b" + type = "CNAME" +}` + +const testAccCheckDigitalOceanRecordConfig_external_cname = ` +resource "digitalocean_domain" "foobar" { + name = "foobar-test-terraform.com" + ip_address = "192.168.0.10" +} + +resource "digitalocean_record" "foobar" { + domain = "${digitalocean_domain.foobar.name}" + + name = "terraform" + value = "a.foobar-test-terraform.net." type = "CNAME" }` From e0df2a948c0bb15fdd534c47fa6db1e20ee3e4c5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 10:58:42 -0700 Subject: [PATCH 111/295] update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 733e2d10c..ad3fa30c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,8 @@ BUG FIXES: * providers/aws: Longer wait times for route53 records (30 mins). [GH-1164] * providers/digitalocean: Waits until droplet is ready to be destroyed [GH-1057] * providers/digitalocean: More lenient about 404's while waiting [GH-1062] + * providers/digitalocean: FQDN for domain records in CNAME, MX, NS, etc. + Also fixes invalid updates in plans. [GH-863] * providers/google: Network data in state was not being stored. [GH-1095] PLUGIN CHANGES: From f51fb5e127a29e403d3afa6024d480e90205b831 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Thu, 26 Mar 2015 12:11:26 -0500 Subject: [PATCH 112/295] providers/aws: handle empty instancestate in state migration fixes #1309 --- .../aws/resource_aws_instance_migrate.go | 6 ++++ .../aws/resource_aws_instance_migrate_test.go | 24 ++++++++++++++++ terraform/state.go | 4 +++ terraform/state_test.go | 28 +++++++++++++++++++ 4 files changed, 62 insertions(+) diff --git a/builtin/providers/aws/resource_aws_instance_migrate.go b/builtin/providers/aws/resource_aws_instance_migrate.go index adb7a01ec..5d7075f75 100644 --- a/builtin/providers/aws/resource_aws_instance_migrate.go +++ b/builtin/providers/aws/resource_aws_instance_migrate.go @@ -24,7 +24,13 @@ func resourceAwsInstanceMigrateState( } func migrateStateV0toV1(is *terraform.InstanceState) (*terraform.InstanceState, error) { + if is.Empty() { + log.Println("[DEBUG] Empty InstanceState; nothing to migrate.") + return is, nil + } + log.Printf("[DEBUG] Attributes before migration: %#v", is.Attributes) + // Delete old count delete(is.Attributes, "block_device.#") diff --git a/builtin/providers/aws/resource_aws_instance_migrate_test.go b/builtin/providers/aws/resource_aws_instance_migrate_test.go index 5738f2508..d39294331 100644 --- a/builtin/providers/aws/resource_aws_instance_migrate_test.go +++ b/builtin/providers/aws/resource_aws_instance_migrate_test.go @@ -115,6 +115,7 @@ func TestAWSInstanceMigrateState(t *testing.T) { for tn, tc := range cases { is := &terraform.InstanceState{ + ID: "i-abc123", Attributes: tc.Attributes, } is, err := resourceAwsInstanceMigrateState( @@ -133,3 +134,26 @@ func TestAWSInstanceMigrateState(t *testing.T) { } } } + +func TestAWSInstanceMigrateState_empty(t *testing.T) { + var is *terraform.InstanceState + var meta interface{} + + // should handle nil + is, err := resourceAwsInstanceMigrateState(0, is, meta) + + if err != nil { + t.Fatalf("err: %#v", err) + } + if is != nil { + t.Fatalf("expected nil instancestate, got: %#v", is) + } + + // should handle non-nil but empty + is = &terraform.InstanceState{} + is, err = resourceAwsInstanceMigrateState(0, is, meta) + + if err != nil { + t.Fatalf("err: %#v", err) + } +} diff --git a/terraform/state.go b/terraform/state.go index 1a1a28bea..d4341302d 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -885,6 +885,10 @@ func (i *InstanceState) deepcopy() *InstanceState { return n } +func (s *InstanceState) Empty() bool { + return s == nil || s.ID == "" +} + func (s *InstanceState) Equal(other *InstanceState) bool { // Short circuit some nil checks if s == nil || other == nil { diff --git a/terraform/state_test.go b/terraform/state_test.go index e222b1b72..7f3dbb567 100644 --- a/terraform/state_test.go +++ b/terraform/state_test.go @@ -366,6 +366,34 @@ func TestResourceStateTaint(t *testing.T) { } } +func TestInstanceStateEmpty(t *testing.T) { + cases := map[string]struct { + In *InstanceState + Result bool + }{ + "nil is empty": { + nil, + true, + }, + "non-nil but without ID is empty": { + &InstanceState{}, + true, + }, + "with ID is not empty": { + &InstanceState{ + ID: "i-abc123", + }, + false, + }, + } + + for tn, tc := range cases { + if tc.In.Empty() != tc.Result { + t.Fatalf("%q expected %#v to be empty: %#v", tn, tc.In, tc.Result) + } + } +} + func TestInstanceStateEqual(t *testing.T) { cases := []struct { Result bool From deda59b50e318485360ae3563815cf682c4f29d8 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 26 Mar 2015 14:49:15 -0500 Subject: [PATCH 113/295] minor code cleanups to get acceptance tests passing --- builtin/providers/aws/autoscaling_tags.go | 4 +-- .../aws/resource_aws_autoscaling_group.go | 16 +++++----- .../resource_aws_autoscaling_group_test.go | 30 +++++++++++-------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/builtin/providers/aws/autoscaling_tags.go b/builtin/providers/aws/autoscaling_tags.go index 508a0ddd8..342caae54 100644 --- a/builtin/providers/aws/autoscaling_tags.go +++ b/builtin/providers/aws/autoscaling_tags.go @@ -150,8 +150,8 @@ func autoscalingTagDescriptionsToMap(ts []autoscaling.TagDescription) map[string tags := make(map[string]map[string]interface{}) for _, t := range ts { tag := map[string]interface{}{ - "value": t.Value, - "propagate_at_launch": t.PropagateAtLaunch, + "value": *t.Value, + "propagate_at_launch": *t.PropagateAtLaunch, } tags[*t.Key] = tag } diff --git a/builtin/providers/aws/resource_aws_autoscaling_group.go b/builtin/providers/aws/resource_aws_autoscaling_group.go index 9e5dc60a5..de3bbe9cc 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group.go @@ -193,15 +193,15 @@ func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) e } d.Set("availability_zones", g.AvailabilityZones) - d.Set("default_cooldown", *g.DefaultCooldown) - d.Set("desired_capacity", *g.DesiredCapacity) - d.Set("health_check_grace_period", *g.HealthCheckGracePeriod) - d.Set("health_check_type", *g.HealthCheckType) - d.Set("launch_configuration", *g.LaunchConfigurationName) + d.Set("default_cooldown", g.DefaultCooldown) + d.Set("desired_capacity", g.DesiredCapacity) + d.Set("health_check_grace_period", g.HealthCheckGracePeriod) + d.Set("health_check_type", g.HealthCheckType) + d.Set("launch_configuration", g.LaunchConfigurationName) d.Set("load_balancers", g.LoadBalancerNames) - d.Set("min_size", *g.MinSize) - d.Set("max_size", *g.MaxSize) - d.Set("name", *g.AutoScalingGroupName) + d.Set("min_size", g.MinSize) + d.Set("max_size", g.MaxSize) + d.Set("name", g.AutoScalingGroupName) d.Set("tag", g.Tags) d.Set("vpc_zone_identifier", strings.Split(*g.VPCZoneIdentifier, ",")) d.Set("termination_policies", g.TerminationPolicies) diff --git a/builtin/providers/aws/resource_aws_autoscaling_group_test.go b/builtin/providers/aws/resource_aws_autoscaling_group_test.go index 2763450f6..661e71fe8 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group_test.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group_test.go @@ -26,7 +26,7 @@ func TestAccAWSAutoScalingGroup_basic(t *testing.T) { testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), testAccCheckAWSAutoScalingGroupAttributes(&group), resource.TestCheckResourceAttr( - "aws_autoscaling_group.bar", "availability_zones.2487133097", "us-west-2a"), + "aws_autoscaling_group.bar", "availability_zones.1807834199", "us-west-2a"), resource.TestCheckResourceAttr( "aws_autoscaling_group.bar", "name", "foobar3-terraform-test"), resource.TestCheckResourceAttr( @@ -54,8 +54,10 @@ func TestAccAWSAutoScalingGroup_basic(t *testing.T) { resource.TestCheckResourceAttr( "aws_autoscaling_group.bar", "desired_capacity", "5"), testLaunchConfigurationName("aws_autoscaling_group.bar", &lc), - resource.TestCheckResourceAttr( - "aws_autoscaling_group.bar", "tag", "xxx"), + testAccCheckAutoscalingTags(&group.Tags, "Bar", map[string]interface{}{ + "value": "bar-foo", + "propagate_at_launch": true, + }), ), }, }, @@ -74,20 +76,20 @@ func TestAccAWSAutoScalingGroup_tags(t *testing.T) { Config: testAccAWSAutoScalingGroupConfig, Check: resource.ComposeTestCheckFunc( testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), - testAccCheckAutoscalingTags(&group.Tags, "foo", map[string]interface{}{ - "value": "bar", + testAccCheckAutoscalingTags(&group.Tags, "Foo", map[string]interface{}{ + "value": "foo-bar", "propagate_at_launch": true, }), ), }, resource.TestStep{ - Config: testAccCheckInstanceConfigTagsUpdate, + Config: testAccAWSAutoScalingGroupConfigUpdate, Check: resource.ComposeTestCheckFunc( testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), - testAccCheckAutoscalingTagNotExists(&group.Tags, "foo"), - testAccCheckAutoscalingTags(&group.Tags, "bar", map[string]interface{}{ - "value": "baz", + testAccCheckAutoscalingTagNotExists(&group.Tags, "Foo"), + testAccCheckAutoscalingTags(&group.Tags, "Bar", map[string]interface{}{ + "value": "bar-foo", "propagate_at_launch": true, }), ), @@ -182,10 +184,12 @@ func testAccCheckAWSAutoScalingGroupAttributes(group *autoscaling.AutoScalingGro return fmt.Errorf("Bad launch configuration name: %s", *group.LaunchConfigurationName) } - t := autoscaling.Tag{ - Key: aws.String("Name"), + t := autoscaling.TagDescription{ + Key: aws.String("Foo"), Value: aws.String("foo-bar"), PropagateAtLaunch: aws.Boolean(true), + ResourceType: aws.String("auto-scaling-group"), + ResourceID: group.AutoScalingGroupName, } if !reflect.DeepEqual(group.Tags[0], t) { @@ -278,7 +282,7 @@ resource "aws_autoscaling_group" "bar" { launch_configuration = "${aws_launch_configuration.foobar.name}" tag { - key = "Name" + key = "Foo" value = "foo-bar" propagate_at_launch = true } @@ -311,7 +315,7 @@ resource "aws_autoscaling_group" "bar" { launch_configuration = "${aws_launch_configuration.new.name}" tag { - key = "Name" + key = "Bar" value = "bar-foo" propagate_at_launch = true } From b64a919d83b44fcba5a6e754401e0429115e1153 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Thu, 26 Mar 2015 16:45:23 -0500 Subject: [PATCH 114/295] provider/aws: Add tags to Route53 hosted zones --- .../aws/resource_aws_route53_zone.go | 34 +++++++- builtin/providers/aws/tags_route53.go | 87 +++++++++++++++++++ builtin/providers/aws/tags_route53_test.go | 85 ++++++++++++++++++ 3 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/aws/tags_route53.go create mode 100644 builtin/providers/aws/tags_route53_test.go diff --git a/builtin/providers/aws/resource_aws_route53_zone.go b/builtin/providers/aws/resource_aws_route53_zone.go index 6d9914b7f..e6c8be571 100644 --- a/builtin/providers/aws/resource_aws_route53_zone.go +++ b/builtin/providers/aws/resource_aws_route53_zone.go @@ -16,6 +16,7 @@ func resourceAwsRoute53Zone() *schema.Resource { return &schema.Resource{ Create: resourceAwsRoute53ZoneCreate, Read: resourceAwsRoute53ZoneRead, + Update: resourceAwsRoute53ZoneUpdate, Delete: resourceAwsRoute53ZoneDelete, Schema: map[string]*schema.Schema{ @@ -29,6 +30,8 @@ func resourceAwsRoute53Zone() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "tags": tagsSchema(), }, } } @@ -72,7 +75,7 @@ func resourceAwsRoute53ZoneCreate(d *schema.ResourceData, meta interface{}) erro if err != nil { return err } - return nil + return resourceAwsRoute53ZoneUpdate(d, meta) } func resourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error { @@ -87,9 +90,38 @@ func resourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error return err } + // get tags + req := &route53.ListTagsForResourceRequest{ + ResourceID: aws.String(d.Id()), + ResourceType: aws.String("hostedzone"), + } + + resp, err := r53.ListTagsForResource(req) + if err != nil { + return err + } + + var tags []route53.Tag + if resp.ResourceTagSet != nil { + tags = resp.ResourceTagSet.Tags + } + d.Set("tags", tagsToMapR53(tags)) + return nil } +func resourceAwsRoute53ZoneUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).r53conn + + if err := setTagsR53(conn, d); err != nil { + return err + } else { + d.SetPartial("tags") + } + + return resourceAwsRoute53ZoneRead(d, meta) +} + func resourceAwsRoute53ZoneDelete(d *schema.ResourceData, meta interface{}) error { r53 := meta.(*AWSClient).r53conn diff --git a/builtin/providers/aws/tags_route53.go b/builtin/providers/aws/tags_route53.go new file mode 100644 index 000000000..374c4bc32 --- /dev/null +++ b/builtin/providers/aws/tags_route53.go @@ -0,0 +1,87 @@ +package aws + +import ( + "log" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/route53" + "github.com/hashicorp/terraform/helper/schema" +) + +// setTags is a helper to set the tags for a resource. It expects the +// tags field to be named "tags" +func setTagsR53(conn *route53.Route53, d *schema.ResourceData) error { + if d.HasChange("tags") { + oraw, nraw := d.GetChange("tags") + o := oraw.(map[string]interface{}) + n := nraw.(map[string]interface{}) + create, remove := diffTagsR53(tagsFromMapR53(o), tagsFromMapR53(n)) + + // Set tags + r := make([]string, len(remove)) + for i, t := range remove { + r[i] = *t.Key + } + log.Printf("[DEBUG] Changing tags: \n\tadding: %#v\n\tremoving:", create, remove) + req := &route53.ChangeTagsForResourceRequest{ + AddTags: create, + RemoveTagKeys: r, + ResourceID: aws.String(d.Id()), + ResourceType: aws.String("hostedzone"), + } + resp, err := conn.ChangeTagsForResource(req) + log.Printf("\n\t****\nresp:\n%#v", resp) + if err != nil { + log.Printf("\n\t****\nerror:\n%#v", err) + return err + } + } + + return nil +} + +// diffTags takes our tags locally and the ones remotely and returns +// the set of tags that must be created, and the set of tags that must +// be destroyed. +func diffTagsR53(oldTags, newTags []route53.Tag) ([]route53.Tag, []route53.Tag) { + // First, we're creating everything we have + create := make(map[string]interface{}) + for _, t := range newTags { + create[*t.Key] = *t.Value + } + + // Build the list of what to remove + var remove []route53.Tag + for _, t := range oldTags { + old, ok := create[*t.Key] + if !ok || old != *t.Value { + // Delete it! + remove = append(remove, t) + } + } + + return tagsFromMapR53(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMapR53(m map[string]interface{}) []route53.Tag { + result := make([]route53.Tag, 0, len(m)) + for k, v := range m { + result = append(result, route53.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + }) + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMapR53(ts []route53.Tag) map[string]string { + result := make(map[string]string) + for _, t := range ts { + result[*t.Key] = *t.Value + } + + return result +} diff --git a/builtin/providers/aws/tags_route53_test.go b/builtin/providers/aws/tags_route53_test.go new file mode 100644 index 000000000..79ce1e513 --- /dev/null +++ b/builtin/providers/aws/tags_route53_test.go @@ -0,0 +1,85 @@ +package aws + +import ( + "fmt" + "reflect" + "testing" + + "github.com/hashicorp/aws-sdk-go/gen/ec2" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestDiffTagsR53(t *testing.T) { + cases := []struct { + Old, New map[string]interface{} + Create, Remove map[string]string + }{ + // Basic add/remove + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "bar": "baz", + }, + Create: map[string]string{ + "bar": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + + // Modify + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "foo": "baz", + }, + Create: map[string]string{ + "foo": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + } + + for i, tc := range cases { + c, r := diffTags(tagsFromMap(tc.Old), tagsFromMap(tc.New)) + cm := tagsToMap(c) + rm := tagsToMap(r) + if !reflect.DeepEqual(cm, tc.Create) { + t.Fatalf("%d: bad create: %#v", i, cm) + } + if !reflect.DeepEqual(rm, tc.Remove) { + t.Fatalf("%d: bad remove: %#v", i, rm) + } + } +} + +// testAccCheckTags can be used to check the tags on a resource. +func testAccCheckTagsR53( + ts *[]ec2.Tag, key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + m := tagsToMap(*ts) + v, ok := m[key] + if value != "" && !ok { + return fmt.Errorf("Missing tag: %s", key) + } else if value == "" && ok { + return fmt.Errorf("Extra tag: %s", key) + } + if value == "" { + return nil + } + + if v != value { + return fmt.Errorf("%s: bad value: %s", key, v) + } + + return nil + } +} From 1adb3fbfa0c45ff93cb88981e13ba1e4d84d5bb4 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Thu, 26 Mar 2015 19:55:18 -0400 Subject: [PATCH 115/295] command: when setting up state, only write back if local is newer --- command/remote_pull_test.go | 24 +++++++++++++++--------- command/state.go | 12 +++++++++++- terraform/state.go | 4 ++++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/command/remote_pull_test.go b/command/remote_pull_test.go index 94b52ce2b..a867877e1 100644 --- a/command/remote_pull_test.go +++ b/command/remote_pull_test.go @@ -80,15 +80,6 @@ func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.Remote var b64md5 string buf := bytes.NewBuffer(nil) - if s != nil { - enc := json.NewEncoder(buf) - if err := enc.Encode(s); err != nil { - t.Fatalf("err: %v", err) - } - md5 := md5.Sum(buf.Bytes()) - b64md5 = base64.StdEncoding.EncodeToString(md5[:16]) - } - cb := func(resp http.ResponseWriter, req *http.Request) { if req.Method == "PUT" { resp.WriteHeader(c) @@ -98,13 +89,28 @@ func testRemoteState(t *testing.T, s *terraform.State, c int) (*terraform.Remote resp.WriteHeader(404) return } + resp.Header().Set("Content-MD5", b64md5) resp.Write(buf.Bytes()) } + srv := httptest.NewServer(http.HandlerFunc(cb)) remote := &terraform.RemoteState{ Type: "http", Config: map[string]string{"address": srv.URL}, } + + if s != nil { + // Set the remote data + s.Remote = remote + + enc := json.NewEncoder(buf) + if err := enc.Encode(s); err != nil { + t.Fatalf("err: %v", err) + } + md5 := md5.Sum(buf.Bytes()) + b64md5 = base64.StdEncoding.EncodeToString(md5[:16]) + } + return remote, srv } diff --git a/command/state.go b/command/state.go index 20cb4c1e4..da34cfb2f 100644 --- a/command/state.go +++ b/command/state.go @@ -231,10 +231,20 @@ func remoteState( "Error reloading remote state: {{err}}", err) } switch cache.RefreshResult() { + // All the results below can be safely ignored since it means the + // pull was successful in some way. Noop = nothing happened. + // Init = both are empty. UpdateLocal = local state was older and + // updated. + // + // We don't have to do anything, the pull was successful. case state.CacheRefreshNoop: case state.CacheRefreshInit: - case state.CacheRefreshLocalNewer: case state.CacheRefreshUpdateLocal: + + // Our local state has a higher serial number than remote, so we + // want to explicitly sync the remote side with our local so that + // the remote gets the latest serial number. + case state.CacheRefreshLocalNewer: // Write our local state out to the durable storage to start. if err := cache.WriteState(local); err != nil { return nil, errwrap.Wrapf( diff --git a/terraform/state.go b/terraform/state.go index 1a1a28bea..258f3f034 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -344,6 +344,10 @@ func (r *RemoteState) Equals(other *RemoteState) bool { return true } +func (r *RemoteState) GoString() string { + return fmt.Sprintf("*%#v", *r) +} + // ModuleState is used to track all the state relevant to a single // module. Previous to Terraform 0.3, all state belonged to the "root" // module. From 35da19cc1ffd781a3970cb1f34de993447230014 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 17:31:33 -0700 Subject: [PATCH 116/295] command/remote-config: remove weird error case that shows no error message /cc @sethvargo --- command/remote_config.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/command/remote_config.go b/command/remote_config.go index 7c06ac19a..46ab3e13b 100644 --- a/command/remote_config.go +++ b/command/remote_config.go @@ -44,12 +44,6 @@ func (c *RemoteConfigCommand) Run(args []string) int { return 1 } - // Show help if given no inputs - if !c.conf.disableRemote && c.remoteConf.Type == "atlas" && len(config) == 0 { - cmdFlags.Usage() - return 1 - } - // Set the local state path c.statePath = c.conf.statePath From 7bfa5afd0005744a1edc5649d6ad9cc799680d42 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 17:33:58 -0700 Subject: [PATCH 117/295] command/remote-config: show flag parse errors /cc @sethvargo --- command/remote_config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/command/remote_config.go b/command/remote_config.go index 46ab3e13b..e2b865a6d 100644 --- a/command/remote_config.go +++ b/command/remote_config.go @@ -41,6 +41,7 @@ func (c *RemoteConfigCommand) Run(args []string) int { cmdFlags.Var((*FlagKV)(&config), "backend-config", "config") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { + c.Ui.Error(fmt.Sprintf("\nError parsing CLI flags: %s", err)) return 1 } From 38b1a727bf7ba95c3c2b9a389ec0f24c8937b7c0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 17:37:05 -0700 Subject: [PATCH 118/295] command/remote-config: lowercase the type so that Atlas works, for example --- command/remote_config.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/command/remote_config.go b/command/remote_config.go index e2b865a6d..fa6d06929 100644 --- a/command/remote_config.go +++ b/command/remote_config.go @@ -45,6 +45,9 @@ func (c *RemoteConfigCommand) Run(args []string) int { return 1 } + // Lowercase the type + c.remoteConf.Type = strings.ToLower(c.remoteConf.Type) + // Set the local state path c.statePath = c.conf.statePath From 6379a888fb51239b9233e71406f77bc6b7b11685 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 17:40:39 -0700 Subject: [PATCH 119/295] command/remote-{pull,push}: colorize and show success output --- command/remote_pull.go | 3 ++- command/remote_push.go | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/command/remote_pull.go b/command/remote_pull.go index 3965f0d42..bf757ccf1 100644 --- a/command/remote_pull.go +++ b/command/remote_pull.go @@ -61,7 +61,8 @@ func (c *RemotePullCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("%s", change)) return 1 } else { - c.Ui.Output(fmt.Sprintf("%s", change)) + c.Ui.Output(c.Colorize().Color(fmt.Sprintf( + "[reset][bold][green]%s", change))) } return 0 diff --git a/command/remote_push.go b/command/remote_push.go index 259c82863..cb6b2249e 100644 --- a/command/remote_push.go +++ b/command/remote_push.go @@ -68,6 +68,8 @@ func (c *RemotePushCommand) Run(args []string) int { return 1 } + c.Ui.Output(c.Colorize().Color( + "[reset][bold][green]State successfully pushed!")) return 0 } From 4a7b554cf757521ee9ddaa570c7d45d219935e95 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 17:57:45 -0700 Subject: [PATCH 120/295] command/remote-config: do a pull with `terraform remote config` --- command/remote_config.go | 53 +++++++++++++++---- .../docs/commands/remote-config.html.markdown | 5 +- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/command/remote_config.go b/command/remote_config.go index fa6d06929..92017c484 100644 --- a/command/remote_config.go +++ b/command/remote_config.go @@ -86,29 +86,63 @@ func (c *RemoteConfigCommand) Run(args []string) int { return c.disableRemoteState() } - // Ensure there is no conflict + // Ensure there is no conflict, and then do the correct operation + var result int haveCache := !remoteState.Empty() haveLocal := !localState.Empty() switch { case haveCache && haveLocal: c.Ui.Error(fmt.Sprintf("Remote state is enabled, but non-managed state file '%s' is also present!", c.conf.statePath)) - return 1 + result = 1 case !haveCache && !haveLocal: // If we don't have either state file, initialize a blank state file - return c.initBlankState() + result = c.initBlankState() case haveCache && !haveLocal: // Update the remote state target potentially - return c.updateRemoteConfig() + result = c.updateRemoteConfig() case !haveCache && haveLocal: // Enable remote state management - return c.enableRemoteState() + result = c.enableRemoteState() } - panic("unhandled case") + // If there was an error, return right away + if result != 0 { + return result + } + + // If we're not pulling, then do nothing + if !c.conf.pullOnDisable { + return result + } + + // Otherwise, refresh the state + stateResult, err := c.StateRaw(c.StateOpts()) + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error while performing the initial pull. The error message is shown\n"+ + "below. Note that remote state was properly configured, so you don't\n"+ + "need to reconfigure. You can now use `push` and `pull` directly.\n"+ + "\n%s", err)) + return 1 + } + + state := stateResult.State + if err := state.RefreshState(); err != nil { + c.Ui.Error(fmt.Sprintf( + "Error while performing the initial pull. The error message is shown\n"+ + "below. Note that remote state was properly configured, so you don't\n"+ + "need to reconfigure. You can now use `push` and `pull` directly.\n"+ + "\n%s", err)) + return 1 + } + + c.Ui.Output(c.Colorize().Color(fmt.Sprintf( + "[reset][bold][green]Remote state configured and pulled."))) + return 0 } // disableRemoteState is used to disable remote state management, @@ -326,9 +360,10 @@ Options: -disable Disables remote state management and migrates the state to the -state path. - -pull=true Controls if the remote state is pulled before disabling. - This defaults to true to ensure the latest state is cached - before disabling. + -pull=true If disabling, this controls if the remote state is + pulled before disabling. If enabling, this controls + if the remote state is pulled after enabling. This + defaults to true. -state=path Path to read state. Defaults to "terraform.tfstate" unless remote state is enabled. diff --git a/website/source/docs/commands/remote-config.html.markdown b/website/source/docs/commands/remote-config.html.markdown index 3fd6c9b17..5e247d540 100644 --- a/website/source/docs/commands/remote-config.html.markdown +++ b/website/source/docs/commands/remote-config.html.markdown @@ -73,8 +73,9 @@ The command-line flags are all optional. The list of available flags are: * `-path=path` - Path of the remote state in Consul. Required for the Consul backend. -* `-pull=true` - Controls if the remote state is pulled before disabling. - This defaults to true to ensure the latest state is cached before disabling. +* `-pull=true` - Controls if the remote state is pulled before disabling + or after enabling. This defaults to true to ensure the latest state + is available under both conditions. * `-state=path` - Path to read state. Defaults to "terraform.tfstate" unless remote state is enabled. From 37d29c6994800f360c38872a8ee752a0624e62dd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 17:58:27 -0700 Subject: [PATCH 121/295] update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad3fa30c9..d4d588d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ IMPROVEMENTS: 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] + * command/remote-config: After enabling remote state, a `pull` is + automatically done initially. * providers/google: Add `size` option to disk blocks for instances. [GH-1284] BUG FIXES: From da7f307e5696c640612173368b8faa4bc68e511a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 26 Mar 2015 18:14:24 -0700 Subject: [PATCH 122/295] command/remote-config: failing tests --- command/{remote_test.go => remote_config_test.go} | 3 +++ 1 file changed, 3 insertions(+) rename command/{remote_test.go => remote_config_test.go} (99%) diff --git a/command/remote_test.go b/command/remote_config_test.go similarity index 99% rename from command/remote_test.go rename to command/remote_config_test.go index 0452e3416..42a2d2d3b 100644 --- a/command/remote_test.go +++ b/command/remote_config_test.go @@ -245,6 +245,7 @@ func TestRemoteConfig_initBlank(t *testing.T) { "-backend=http", "-backend-config", "address=http://example.com", "-backend-config", "access_token=test", + "-pull=false", } if code := c.Run(args); code != 0 { t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) @@ -321,6 +322,7 @@ func TestRemoteConfig_updateRemote(t *testing.T) { "-backend=http", "-backend-config", "address=http://example.com", "-backend-config", "access_token=test", + "-pull=false", } if code := c.Run(args); code != 0 { t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) @@ -376,6 +378,7 @@ func TestRemoteConfig_enableRemote(t *testing.T) { "-backend=http", "-backend-config", "address=http://example.com", "-backend-config", "access_token=test", + "-pull=false", } if code := c.Run(args); code != 0 { t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) From 043a4848ee88fe06c58235a1f07d5787a11a993f Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 27 Mar 2015 11:35:10 -0500 Subject: [PATCH 123/295] provider/aws: Fix dependency violation when deleting Internet Gateways --- .../aws/resource_aws_internet_gateway.go | 63 +++++++++---------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/builtin/providers/aws/resource_aws_internet_gateway.go b/builtin/providers/aws/resource_aws_internet_gateway.go index 9546ffb5c..e4270d6bc 100644 --- a/builtin/providers/aws/resource_aws_internet_gateway.go +++ b/builtin/providers/aws/resource_aws_internet_gateway.go @@ -199,39 +199,14 @@ func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{}) d.Id(), vpcID.(string)) - wait := true - err := ec2conn.DetachInternetGateway(&ec2.DetachInternetGatewayRequest{ - InternetGatewayID: aws.String(d.Id()), - VPCID: aws.String(vpcID.(string)), - }) - if err != nil { - ec2err, ok := err.(aws.APIError) - if ok { - if ec2err.Code == "InvalidInternetGatewayID.NotFound" { - err = nil - wait = false - } else if ec2err.Code == "Gateway.NotAttached" { - 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 internet gateway (%s) to detach", d.Id()) stateConf := &resource.StateChangeConf{ - Pending: []string{"attached", "detaching", "available"}, + Pending: []string{"detaching"}, Target: "detached", - Refresh: IGAttachStateRefreshFunc(ec2conn, d.Id(), "detached"), - Timeout: 1 * time.Minute, + Refresh: detachIGStateRefreshFunc(ec2conn, d.Id(), vpcID.(string)), + Timeout: 2 * time.Minute, + Delay: 10 * time.Second, } if _, err := stateConf.WaitForState(); err != nil { return fmt.Errorf( @@ -242,6 +217,32 @@ func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{}) return nil } +// InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// an EC2 instance. +func detachIGStateRefreshFunc(conn *ec2.EC2, instanceID, vpcID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + err := conn.DetachInternetGateway(&ec2.DetachInternetGatewayRequest{ + InternetGatewayID: aws.String(instanceID), + VPCID: aws.String(vpcID), + }) + if err != nil { + ec2err, ok := err.(aws.APIError) + if ok { + if ec2err.Code == "InvalidInternetGatewayID.NotFound" { + return nil, "Not Found", err + } else if ec2err.Code == "Gateway.NotAttached" { + return "detached", "detached", nil + } else if ec2err.Code == "DependencyViolation" { + return nil, "detaching", nil + } + } + } + // DetachInternetGateway only returns an error, so if it's nil, assume we're + // detached + return "detached", "detached", nil + } +} + // IGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch // an internet gateway. func IGStateRefreshFunc(ec2conn *ec2.EC2, id string) resource.StateRefreshFunc { @@ -300,10 +301,6 @@ func IGAttachStateRefreshFunc(ec2conn *ec2.EC2, id string, expected string) reso ig := &resp.InternetGateways[0] - if time.Now().Sub(start) > 10*time.Second { - return ig, expected, nil - } - if len(ig.Attachments) == 0 { // No attachments, we're detached return ig, "detached", nil From ce29a87f859c2008ddf1cc35f82f077dfe8e6e98 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 27 Mar 2015 12:14:56 -0700 Subject: [PATCH 124/295] update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4d588d8c..0adfb1f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,8 @@ BUG FIXES: a computed attribute was used as part of a set parameter. [GH-1073] * core: Fix edge case where state containing both "resource" and "resource.0" would ignore the latter completely. [GH-1086] + * core: Modules with a source of a relative file path moving up + directories work properly, i.e. "../a" [GH-1232] * providers/aws: manually deleted VPC removes it from the state * providers/aws: `source_dest_check` regression fixed (now works). [GH-1020] * providers/aws: Longer wait times for DB instances. From f3d8fb1988dacf4593181fd04fcde16c26548f5c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 27 Mar 2015 12:17:57 -0700 Subject: [PATCH 125/295] update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0adfb1f3d..83915244d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ BUG FIXES: * providers/digitalocean: FQDN for domain records in CNAME, MX, NS, etc. Also fixes invalid updates in plans. [GH-863] * providers/google: Network data in state was not being stored. [GH-1095] + * providers/heroku: Fix panic when config vars block was empty. [GH-1211] PLUGIN CHANGES: From 12585b19638bd5eb06a5edf88bb3f0ad7e16c39e Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 27 Mar 2015 15:41:42 -0500 Subject: [PATCH 126/295] provider/aws: Finish Tag support for Route 53 zone --- .../aws/resource_aws_route53_zone_test.go | 43 +++++++++++++++++-- builtin/providers/aws/tags_route53.go | 4 +- builtin/providers/aws/tags_route53_test.go | 12 +++--- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/builtin/providers/aws/resource_aws_route53_zone_test.go b/builtin/providers/aws/resource_aws_route53_zone_test.go index fa78634cf..d6e4af2f2 100644 --- a/builtin/providers/aws/resource_aws_route53_zone_test.go +++ b/builtin/providers/aws/resource_aws_route53_zone_test.go @@ -63,6 +63,9 @@ func TestCleanChangeID(t *testing.T) { } func TestAccRoute53Zone(t *testing.T) { + var zone route53.HostedZone + var td route53.ResourceTagSet + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -71,7 +74,9 @@ func TestAccRoute53Zone(t *testing.T) { resource.TestStep{ Config: testAccRoute53ZoneConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckRoute53ZoneExists("aws_route53_zone.main"), + testAccCheckRoute53ZoneExists("aws_route53_zone.main", &zone), + testAccLoadTagsR53(&zone, &td), + testAccCheckTagsR53(&td.Tags, "foo", "bar"), ), }, }, @@ -93,7 +98,7 @@ func testAccCheckRoute53ZoneDestroy(s *terraform.State) error { return nil } -func testAccCheckRoute53ZoneExists(n string) resource.TestCheckFunc { +func testAccCheckRoute53ZoneExists(n string, zone *route53.HostedZone) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -105,10 +110,37 @@ func testAccCheckRoute53ZoneExists(n string) resource.TestCheckFunc { } conn := testAccProvider.Meta().(*AWSClient).r53conn - _, err := conn.GetHostedZone(&route53.GetHostedZoneRequest{ID: aws.String(rs.Primary.ID)}) + resp, err := conn.GetHostedZone(&route53.GetHostedZoneRequest{ID: aws.String(rs.Primary.ID)}) if err != nil { return fmt.Errorf("Hosted zone err: %v", err) } + *zone = *resp.HostedZone + return nil + } +} + +func testAccLoadTagsR53(zone *route53.HostedZone, td *route53.ResourceTagSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).r53conn + + zone := cleanZoneID(*zone.ID) + req := &route53.ListTagsForResourceRequest{ + ResourceID: aws.String(zone), + ResourceType: aws.String("hostedzone"), + } + + resp, err := conn.ListTagsForResource(req) + if err != nil { + return err + } + + var tags []route53.Tag + if resp.ResourceTagSet != nil { + tags = resp.ResourceTagSet.Tags + } + + *td = *resp.ResourceTagSet + return nil } } @@ -116,5 +148,10 @@ func testAccCheckRoute53ZoneExists(n string) resource.TestCheckFunc { const testAccRoute53ZoneConfig = ` resource "aws_route53_zone" "main" { name = "hashicorp.com" + + tags { + foo = "bar" + Name = "tf-route53-tag-test" + } } ` diff --git a/builtin/providers/aws/tags_route53.go b/builtin/providers/aws/tags_route53.go index 374c4bc32..8e31fc373 100644 --- a/builtin/providers/aws/tags_route53.go +++ b/builtin/providers/aws/tags_route53.go @@ -22,7 +22,7 @@ func setTagsR53(conn *route53.Route53, d *schema.ResourceData) error { for i, t := range remove { r[i] = *t.Key } - log.Printf("[DEBUG] Changing tags: \n\tadding: %#v\n\tremoving:", create, remove) + log.Printf("[DEBUG] Changing tags: \n\tadding: %#v\n\tremoving:%#v", create, remove) req := &route53.ChangeTagsForResourceRequest{ AddTags: create, RemoveTagKeys: r, @@ -30,9 +30,7 @@ func setTagsR53(conn *route53.Route53, d *schema.ResourceData) error { ResourceType: aws.String("hostedzone"), } resp, err := conn.ChangeTagsForResource(req) - log.Printf("\n\t****\nresp:\n%#v", resp) if err != nil { - log.Printf("\n\t****\nerror:\n%#v", err) return err } } diff --git a/builtin/providers/aws/tags_route53_test.go b/builtin/providers/aws/tags_route53_test.go index 79ce1e513..40a4154f3 100644 --- a/builtin/providers/aws/tags_route53_test.go +++ b/builtin/providers/aws/tags_route53_test.go @@ -5,7 +5,7 @@ import ( "reflect" "testing" - "github.com/hashicorp/aws-sdk-go/gen/ec2" + "github.com/hashicorp/aws-sdk-go/gen/route53" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) @@ -49,9 +49,9 @@ func TestDiffTagsR53(t *testing.T) { } for i, tc := range cases { - c, r := diffTags(tagsFromMap(tc.Old), tagsFromMap(tc.New)) - cm := tagsToMap(c) - rm := tagsToMap(r) + c, r := diffTagsR53(tagsFromMapR53(tc.Old), tagsFromMapR53(tc.New)) + cm := tagsToMapR53(c) + rm := tagsToMapR53(r) if !reflect.DeepEqual(cm, tc.Create) { t.Fatalf("%d: bad create: %#v", i, cm) } @@ -63,9 +63,9 @@ func TestDiffTagsR53(t *testing.T) { // testAccCheckTags can be used to check the tags on a resource. func testAccCheckTagsR53( - ts *[]ec2.Tag, key string, value string) resource.TestCheckFunc { + ts *[]route53.Tag, key string, value string) resource.TestCheckFunc { return func(s *terraform.State) error { - m := tagsToMap(*ts) + m := tagsToMapR53(*ts) v, ok := m[key] if value != "" && !ok { return fmt.Errorf("Missing tag: %s", key) From ce8ec26d0803ce31c40c9403a1acc0a2add8847c Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 27 Mar 2015 16:05:54 -0500 Subject: [PATCH 127/295] cleanups --- builtin/providers/aws/resource_aws_route53_zone_test.go | 5 +---- builtin/providers/aws/tags_route53.go | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/builtin/providers/aws/resource_aws_route53_zone_test.go b/builtin/providers/aws/resource_aws_route53_zone_test.go index d6e4af2f2..0669f88b1 100644 --- a/builtin/providers/aws/resource_aws_route53_zone_test.go +++ b/builtin/providers/aws/resource_aws_route53_zone_test.go @@ -134,13 +134,10 @@ func testAccLoadTagsR53(zone *route53.HostedZone, td *route53.ResourceTagSet) re return err } - var tags []route53.Tag if resp.ResourceTagSet != nil { - tags = resp.ResourceTagSet.Tags + *td = *resp.ResourceTagSet } - *td = *resp.ResourceTagSet - return nil } } diff --git a/builtin/providers/aws/tags_route53.go b/builtin/providers/aws/tags_route53.go index 8e31fc373..e5251d02a 100644 --- a/builtin/providers/aws/tags_route53.go +++ b/builtin/providers/aws/tags_route53.go @@ -29,7 +29,8 @@ func setTagsR53(conn *route53.Route53, d *schema.ResourceData) error { ResourceID: aws.String(d.Id()), ResourceType: aws.String("hostedzone"), } - resp, err := conn.ChangeTagsForResource(req) + + _, err := conn.ChangeTagsForResource(req) if err != nil { return err } From f14024a772800bcd3c9479bc18706a2cc2f5676a Mon Sep 17 00:00:00 2001 From: Clint Date: Fri, 27 Mar 2015 16:37:25 -0500 Subject: [PATCH 128/295] Update CHANGELOG with post 0.3.7 changes When through the PRs list and added changes / fixes that weren't themselves introduced post 0.3.7 --- CHANGELOG.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83915244d..e3864b252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ FEATURES: * **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] + * **New resource: `aws_elastic_network_interfaces`** [GH-1149] * **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 @@ -23,6 +24,8 @@ FEATURES: or system killing Terraform. * **Math operations** in interpolations. You can now do things like `${count.index+1}`. [GH-1068] + * **New AWS SDK:** Move to `aws-sdk-go` (hashicorp/aws-sdk-go), + a fork of the offical `awslabs` repo. IMPROVEMENTS: @@ -34,7 +37,6 @@ 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. @@ -43,6 +45,11 @@ IMPROVEMENTS: * command/remote-config: After enabling remote state, a `pull` is automatically done initially. * providers/google: Add `size` option to disk blocks for instances. [GH-1284] + * providers/aws: Improve support for tagging resources. + * providers/aws: Add a short syntax for Route 53 Record names, e.g. `www` instead of + `www.example.com`. + * providers/aws: Improve dependency violation error handling, when deleting + Internet Gateways or Auto Scaling groups [GH-1325]. BUG FIXES: @@ -61,6 +68,14 @@ BUG FIXES: * providers/aws: `source_dest_check` regression fixed (now works). [GH-1020] * providers/aws: Longer wait times for DB instances. * providers/aws: Longer wait times for route53 records (30 mins). [GH-1164] + * providers/aws: Fix support for TXT records in Route 53. [GH-1213] + * providers/aws: Fix support for wildcard records in Route 53. [GH-1222] + * providers/aws: Fix issue with ignoring the 'self' attribute of a Security Group rule. [GH-1223] + * providers/aws: Fix issue with `sql_mode` in RDS parameter group always causing an update. [GH-1225] + * providers/aws: Fix dependency violation with subnets and security groups [GH-1252] + * providers/aws: Fix issue with refreshing `db_subnet_groups` causing an error instead of updating state [GH-1254] + * providers/aws: Prevent empty string to be used as default health_check_type [GH-1052] + * providers/aws: Add tags on AWS IG creation, not just on update [GH-1176] * providers/digitalocean: Waits until droplet is ready to be destroyed [GH-1057] * providers/digitalocean: More lenient about 404's while waiting [GH-1062] * providers/digitalocean: FQDN for domain records in CNAME, MX, NS, etc. From fab68b304bc2673fd64d1f347b06cd22ec107c56 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 27 Mar 2015 16:44:29 -0500 Subject: [PATCH 129/295] updates --- CHANGELOG.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3864b252..d4209dc0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,14 +18,16 @@ FEATURES: * **New resource: `aws_elastic_network_interfaces`** [GH-1149] * **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 - continously updated as apply is running, meaning that the state is + * **Continuous state** saving during `terraform apply`. The state file is + continuously updated as apply is running, meaning that the state is less likely to become corrupt in a catastrophic case: terraform panic or system killing Terraform. * **Math operations** in interpolations. You can now do things like `${count.index+1}`. [GH-1068] * **New AWS SDK:** Move to `aws-sdk-go` (hashicorp/aws-sdk-go), - a fork of the offical `awslabs` repo. + a fork of the offical `awslabs` repo. We forked for stability while + `awslabs` refactored the library, and will move back to the officially + supported version in the next release. IMPROVEMENTS: @@ -46,8 +48,8 @@ IMPROVEMENTS: automatically done initially. * providers/google: Add `size` option to disk blocks for instances. [GH-1284] * providers/aws: Improve support for tagging resources. - * providers/aws: Add a short syntax for Route 53 Record names, e.g. `www` instead of - `www.example.com`. + * providers/aws: Add a short syntax for Route 53 Record names, e.g. + `www` instead of `www.example.com`. * providers/aws: Improve dependency violation error handling, when deleting Internet Gateways or Auto Scaling groups [GH-1325]. @@ -70,11 +72,16 @@ BUG FIXES: * providers/aws: Longer wait times for route53 records (30 mins). [GH-1164] * providers/aws: Fix support for TXT records in Route 53. [GH-1213] * providers/aws: Fix support for wildcard records in Route 53. [GH-1222] - * providers/aws: Fix issue with ignoring the 'self' attribute of a Security Group rule. [GH-1223] - * providers/aws: Fix issue with `sql_mode` in RDS parameter group always causing an update. [GH-1225] - * providers/aws: Fix dependency violation with subnets and security groups [GH-1252] - * providers/aws: Fix issue with refreshing `db_subnet_groups` causing an error instead of updating state [GH-1254] - * providers/aws: Prevent empty string to be used as default health_check_type [GH-1052] + * providers/aws: Fix issue with ignoring the 'self' attribute of a + Security Group rule. [GH-1223] + * providers/aws: Fix issue with `sql_mode` in RDS parameter group always + causing an update. [GH-1225] + * providers/aws: Fix dependency violation with subnets and security groups + [GH-1252] + * providers/aws: Fix issue with refreshing `db_subnet_groups` causing an error + instead of updating state [GH-1254] + * providers/aws: Prevent empty string to be used as default + `health_check_type` [GH-1052] * providers/aws: Add tags on AWS IG creation, not just on update [GH-1176] * providers/digitalocean: Waits until droplet is ready to be destroyed [GH-1057] * providers/digitalocean: More lenient about 404's while waiting [GH-1062] @@ -108,7 +115,7 @@ IMPROVEMENTS: * provider/aws: The `aws_db_instance` resource no longer requires both `final_snapshot_identifier` and `skip_final_snapshot`; the presence or absence of the former now implies the latter. [GH-874] - * provider/aws: Avoid unecessary update of `aws_subnet` when + * provider/aws: Avoid unnecessary update of `aws_subnet` when `map_public_ip_on_launch` is not specified in config. [GH-898] * provider/aws: Add `apply_method` to `aws_db_parameter_group` [GH-897] * provider/aws: Add `storage_type` to `aws_db_instance` [GH-896] @@ -141,7 +148,7 @@ BUG FIXES: * command/apply: Fix regression where user variables weren't asked [GH-736] * helper/hashcode: Update `hash.String()` to always return a positive index. Fixes issue where specific strings would convert to a negative index - and be ommited when creating Route53 records. [GH-967] + and be omitted when creating Route53 records. [GH-967] * provider/aws: Automatically suffix the Route53 zone name on record names. [GH-312] * provider/aws: Instance should ignore root EBS devices. [GH-877] * provider/aws: Fix `aws_db_instance` to not recreate each time. [GH-874] @@ -547,3 +554,4 @@ BUG FIXES: * Initial release + From d6303c91adf213f4293fd42832d7b91781c57254 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 27 Mar 2015 15:18:52 -0700 Subject: [PATCH 130/295] providers/docker: support DOCKER_CERT_PATH --- builtin/providers/docker/config.go | 22 ++++++++++++++++++---- builtin/providers/docker/provider.go | 14 +++++++++++--- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/builtin/providers/docker/config.go b/builtin/providers/docker/config.go index 40355b24f..ed13314a2 100644 --- a/builtin/providers/docker/config.go +++ b/builtin/providers/docker/config.go @@ -1,10 +1,15 @@ package docker -import dc "github.com/fsouza/go-dockerclient" +import ( + "path/filepath" + + dc "github.com/fsouza/go-dockerclient" +) type Config struct { - DockerHost string - SkipPull bool + Host string + CertPath string + SkipPull bool } type Data struct { @@ -13,7 +18,16 @@ type Data struct { // NewClient() returns a new Docker client. func (c *Config) NewClient() (*dc.Client, error) { - return dc.NewClient(c.DockerHost) + // If there is no cert information, then just return the direct client + if c.CertPath == "" { + return dc.NewClient(c.Host) + } + + // If there is cert information, load it and use it. + ca := filepath.Join(c.CertPath, "ca.pem") + cert := filepath.Join(c.CertPath, "cert.pem") + key := filepath.Join(c.CertPath, "key.pem") + return dc.NewTLSClient(c.Host, cert, key, ca) } // NewData() returns a new data struct. diff --git a/builtin/providers/docker/provider.go b/builtin/providers/docker/provider.go index d01ec385c..77da4bf15 100644 --- a/builtin/providers/docker/provider.go +++ b/builtin/providers/docker/provider.go @@ -8,11 +8,18 @@ import ( func Provider() terraform.ResourceProvider { return &schema.Provider{ Schema: map[string]*schema.Schema{ - "docker_host": &schema.Schema{ + "host": &schema.Schema{ Type: schema.TypeString, Required: true, DefaultFunc: schema.EnvDefaultFunc("DOCKER_HOST", "unix:/run/docker.sock"), - Description: "The Docker daemon endpoint", + Description: "The Docker daemon address", + }, + + "cert_path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("DOCKER_CERT_PATH", nil), + Description: "Path to directory with Docker TLS config", }, }, @@ -27,7 +34,8 @@ func Provider() terraform.ResourceProvider { func providerConfigure(d *schema.ResourceData) (interface{}, error) { config := Config{ - DockerHost: d.Get("docker_host").(string), + Host: d.Get("host").(string), + CertPath: d.Get("cert_path").(string), } return &config, nil From 3601e9f5ee21de7f954271f6cbaceaf597ec73d8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 27 Mar 2015 15:22:33 -0700 Subject: [PATCH 131/295] providers/docker: docker_image acceptance test --- builtin/providers/docker/provider_test.go | 36 +++++++++++++++++++ .../docker/resource_docker_image_test.go | 32 +++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 builtin/providers/docker/provider_test.go create mode 100644 builtin/providers/docker/resource_docker_image_test.go diff --git a/builtin/providers/docker/provider_test.go b/builtin/providers/docker/provider_test.go new file mode 100644 index 000000000..d09104889 --- /dev/null +++ b/builtin/providers/docker/provider_test.go @@ -0,0 +1,36 @@ +package docker + +import ( + "os/exec" + "testing" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +var testAccProviders map[string]terraform.ResourceProvider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider().(*schema.Provider) + testAccProviders = map[string]terraform.ResourceProvider{ + "docker": testAccProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvider_impl(t *testing.T) { + var _ terraform.ResourceProvider = Provider() +} + +func testAccPreCheck(t *testing.T) { + cmd := exec.Command("docker", "version") + if err := cmd.Run(); err != nil { + t.Fatalf("Docker must be available: %s", err) + } +} diff --git a/builtin/providers/docker/resource_docker_image_test.go b/builtin/providers/docker/resource_docker_image_test.go new file mode 100644 index 000000000..d43c81efc --- /dev/null +++ b/builtin/providers/docker/resource_docker_image_test.go @@ -0,0 +1,32 @@ +package docker + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDockerImage_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDockerImageConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "docker_image.foo", + "latest", + "d0955f21bf24f5bfffd32d2d0bb669d0564701c271bc3dfc64cfc5adfdec2d07"), + ), + }, + }, + }) +} + +const testAccDockerImageConfig = ` +resource "docker_image" "foo" { + name = "ubuntu:trusty-20150320" + keep_updated = true +} +` From 7c253155c1a858c3f87d92feae04411decda1b0f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 27 Mar 2015 15:33:17 -0700 Subject: [PATCH 132/295] providers/docker: container acceptance tests --- .../docker/resource_docker_container_test.go | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 builtin/providers/docker/resource_docker_container_test.go diff --git a/builtin/providers/docker/resource_docker_container_test.go b/builtin/providers/docker/resource_docker_container_test.go new file mode 100644 index 000000000..9e2ef6419 --- /dev/null +++ b/builtin/providers/docker/resource_docker_container_test.go @@ -0,0 +1,36 @@ +package docker + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDockerContainer_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDockerContainerConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "docker_image.foo", + "latest", + "d0955f21bf24f5bfffd32d2d0bb669d0564701c271bc3dfc64cfc5adfdec2d07"), + ), + }, + }, + }) +} + +const testAccDockerContainerConfig = ` +resource "docker_image" "foo" { + name = "ubuntu:trusty-20150320" +} + +resource "docker_container" "foo" { + name = "tf-test" + image = "${docker_image.foo.latest}" +} +` From 09333e5e769d62a52bc4097556779871b46c1098 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Mar 2015 18:37:20 -0700 Subject: [PATCH 133/295] providres/docker: cache client --- builtin/providers/docker/config.go | 15 ++++------ builtin/providers/docker/provider.go | 2 +- .../docker/resource_docker_container_funcs.go | 27 ++++-------------- .../docker/resource_docker_image_funcs.go | 28 ++++++++----------- 4 files changed, 24 insertions(+), 48 deletions(-) diff --git a/builtin/providers/docker/config.go b/builtin/providers/docker/config.go index ed13314a2..199182744 100644 --- a/builtin/providers/docker/config.go +++ b/builtin/providers/docker/config.go @@ -6,14 +6,11 @@ import ( dc "github.com/fsouza/go-dockerclient" ) +// Config is the structure that stores the configuration to talk to a +// Docker API compatible host. type Config struct { Host string CertPath string - SkipPull bool -} - -type Data struct { - DockerImages map[string]*dc.APIImages } // NewClient() returns a new Docker client. @@ -30,9 +27,7 @@ func (c *Config) NewClient() (*dc.Client, error) { return dc.NewTLSClient(c.Host, cert, key, ca) } -// NewData() returns a new data struct. -func (c *Config) NewData() *Data { - return &Data{ - DockerImages: map[string]*dc.APIImages{}, - } +// Data ia structure for holding data that we fetch from Docker. +type Data struct { + DockerImages map[string]*dc.APIImages } diff --git a/builtin/providers/docker/provider.go b/builtin/providers/docker/provider.go index 77da4bf15..15bc840a4 100644 --- a/builtin/providers/docker/provider.go +++ b/builtin/providers/docker/provider.go @@ -38,5 +38,5 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { CertPath: d.Get("cert_path").(string), } - return &config, nil + return config.NewClient() } diff --git a/builtin/providers/docker/resource_docker_container_funcs.go b/builtin/providers/docker/resource_docker_container_funcs.go index d0daa08bb..17a8e4eed 100644 --- a/builtin/providers/docker/resource_docker_container_funcs.go +++ b/builtin/providers/docker/resource_docker_container_funcs.go @@ -11,16 +11,11 @@ import ( ) func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) + var err error + client := meta.(*dc.Client) - client, err := config.NewClient() - if err != nil { - return fmt.Errorf("Unable to connect to Docker: %s", err) - } - - data := config.NewData() - - if err := fetchLocalImages(data, client); err != nil { + var data Data + if err := fetchLocalImages(&data, client); err != nil { return err } @@ -116,12 +111,7 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err } func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - - client, err := config.NewClient() - if err != nil { - return fmt.Errorf("Unable to connect to Docker: %s", err) - } + client := meta.(*dc.Client) apiContainer, err := fetchDockerContainer(d.Get("name").(string), client) if err != nil { @@ -152,12 +142,7 @@ func resourceDockerContainerUpdate(d *schema.ResourceData, meta interface{}) err } func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - - client, err := config.NewClient() - if err != nil { - return fmt.Errorf("Unable to connect to Docker: %s", err) - } + client := meta.(*dc.Client) removeOpts := dc.RemoveContainerOptions{ ID: d.Id(), diff --git a/builtin/providers/docker/resource_docker_image_funcs.go b/builtin/providers/docker/resource_docker_image_funcs.go index a34f21994..2c7470db0 100644 --- a/builtin/providers/docker/resource_docker_image_funcs.go +++ b/builtin/providers/docker/resource_docker_image_funcs.go @@ -9,9 +9,8 @@ import ( ) func resourceDockerImageCreate(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - - apiImage, err := findImage(d, config) + client := meta.(*dc.Client) + apiImage, err := findImage(d, client) if err != nil { return fmt.Errorf("Unable to read Docker image into resource: %s", err) } @@ -23,9 +22,8 @@ func resourceDockerImageCreate(d *schema.ResourceData, meta interface{}) error { } func resourceDockerImageRead(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - - apiImage, err := findImage(d, config) + client := meta.(*dc.Client) + apiImage, err := findImage(d, client) if err != nil { return fmt.Errorf("Unable to read Docker image into resource: %s", err) } @@ -53,6 +51,10 @@ func fetchLocalImages(data *Data, client *dc.Client) error { return fmt.Errorf("Unable to list Docker images: %s", err) } + if data.DockerImages == nil { + data.DockerImages = make(map[string]*dc.APIImages) + } + // Docker uses different nomenclatures in different places...sometimes a short // ID, sometimes long, etc. So we store both in the map so we can always find // the same image object. We store the tags, too. @@ -132,15 +134,9 @@ func getImageTag(image string) string { return "" } -func findImage(d *schema.ResourceData, config *Config) (*dc.APIImages, error) { - client, err := config.NewClient() - if err != nil { - return nil, fmt.Errorf("Unable to connect to Docker: %s", err) - } - - data := config.NewData() - - if err := fetchLocalImages(data, client); err != nil { +func findImage(d *schema.ResourceData, client *dc.Client) (*dc.APIImages, error) { + var data Data + if err := fetchLocalImages(&data, client); err != nil { return nil, err } @@ -163,7 +159,7 @@ func findImage(d *schema.ResourceData, config *Config) (*dc.APIImages, error) { foundImage := searchLocal() if d.Get("keep_updated").(bool) || foundImage == nil { - if err := pullImage(data, client, imageName); err != nil { + if err := pullImage(&data, client, imageName); err != nil { return nil, fmt.Errorf("Unable to pull image %s: %s", imageName, err) } } From a7a5d2e5649775019fbb2d97c213a58b193a3739 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Mar 2015 18:45:36 -0700 Subject: [PATCH 134/295] providers/docker: make container test better --- .../docker/resource_docker_container_test.go | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/builtin/providers/docker/resource_docker_container_test.go b/builtin/providers/docker/resource_docker_container_test.go index 9e2ef6419..48302d096 100644 --- a/builtin/providers/docker/resource_docker_container_test.go +++ b/builtin/providers/docker/resource_docker_container_test.go @@ -1,9 +1,12 @@ package docker import ( + "fmt" "testing" + dc "github.com/fsouza/go-dockerclient" "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" ) func TestAccDockerContainer_basic(t *testing.T) { @@ -14,16 +17,40 @@ func TestAccDockerContainer_basic(t *testing.T) { resource.TestStep{ Config: testAccDockerContainerConfig, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr( - "docker_image.foo", - "latest", - "d0955f21bf24f5bfffd32d2d0bb669d0564701c271bc3dfc64cfc5adfdec2d07"), + testAccContainerRunning("docker_container.foo"), ), }, }, }) } +func testAccContainerRunning(n string) 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") + } + + client := testAccProvider.Meta().(*dc.Client) + containers, err := client.ListContainers(dc.ListContainersOptions{}) + if err != nil { + return err + } + + for _, c := range containers { + if c.ID == rs.Primary.ID { + return nil + } + } + + return fmt.Errorf("Container not found: %s", rs.Primary.ID) + } +} + const testAccDockerContainerConfig = ` resource "docker_image" "foo" { name = "ubuntu:trusty-20150320" From 2f0235b23daee1cceeacf8f845e4c69f8b92d79b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Mar 2015 19:05:17 -0700 Subject: [PATCH 135/295] website: docker docs --- website/source/assets/stylesheets/_docs.scss | 1 + .../docs/providers/docker/index.html.markdown | 47 +++++++++++ .../docker/r/container.html.markdown | 77 +++++++++++++++++++ .../providers/docker/r/image.html.markdown | 41 ++++++++++ website/source/layouts/docker.erb | 30 ++++++++ website/source/layouts/docs.erb | 6 +- 6 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 website/source/docs/providers/docker/index.html.markdown create mode 100644 website/source/docs/providers/docker/r/container.html.markdown create mode 100644 website/source/docs/providers/docker/r/image.html.markdown create mode 100644 website/source/layouts/docker.erb diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index f144f813a..27cbba873 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -10,6 +10,7 @@ body.layout-atlas, body.layout-consul, body.layout-dnsimple, body.layout-dme, +body.layout-docker, body.layout-cloudflare, body.layout-cloudstack, body.layout-google, diff --git a/website/source/docs/providers/docker/index.html.markdown b/website/source/docs/providers/docker/index.html.markdown new file mode 100644 index 000000000..73807c12e --- /dev/null +++ b/website/source/docs/providers/docker/index.html.markdown @@ -0,0 +1,47 @@ +--- +layout: "docker" +page_title: "Provider: Docker" +sidebar_current: "docs-docker-index" +description: |- + The Docker provider is used to interact with Docker containers and images. +--- + +# Docker Provider + +The Docker provider is used to interact with Docker containers and images. +It uses the Docker API to manage the lifecycle of Docker containers. Because +the Docker provider uses the Docker API, it is immediatel compatible not +only with single server Docker but Swarm and any additional Docker-compatible +API hosts. + +Use the navigation to the left to read about the available resources. + +## Example Usage + +``` +# Configure the Docker provider +provider "docker" { + host = "tcp://127.0.0.1:1234/" +} + +# Create a container +resource "docker_container" "foo" { + image = "${docker_image.ubuntu.latest}" + name = "foo" +} + +resource "docker_image" "ubuntu" { + name = "ubuntu:latest" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `host` - (Required) This is the address to the Docker host. If this is + blank, the `DOCKER_HOST` environment variable will also be read. + +* `cert_path` - (Optional) Path to a directory with certificate information + for connecting to the Docker host via TLS. If this is blank, the + `DOCKER_CERT_PATH` will also be checked. diff --git a/website/source/docs/providers/docker/r/container.html.markdown b/website/source/docs/providers/docker/r/container.html.markdown new file mode 100644 index 000000000..418e35fc1 --- /dev/null +++ b/website/source/docs/providers/docker/r/container.html.markdown @@ -0,0 +1,77 @@ +--- +layout: "docker" +page_title: "Docker: docker_container" +sidebar_current: "docs-docker-resource-container" +description: |- + Manages the lifecycle of a Docker container. +--- + +# docker\_container + +Manages the lifecycle of a Docker container. + +## Example Usage + +``` +# Start a container +resource "docker_container" "ubuntu" { + name = "foo" + image = "${docker_image.ubuntu.latest}" +} + +# Find the latest Ubuntu precise image. +resource "docker_image" "ubuntu" { + image = "ubuntu:precise" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required, string) The name of the Docker container. +* `image` - (Required, string) The ID of the image to back this container. + The easiest way to get this value is to use the `docker_image` resource + as is shown in the example above. + +* `command` - (Optional, list of strings) The command to use to start the + container. +* `dns` - (Optional, set of strings) Set of DNS servers. +* `env` - (Optional, set of strings) Environmental variables to set. +* `hostname` - (Optional, string) Hostname of the container. +* `domainname` - (Optional, string) Domain name of the container. +* `must_run` - (Optional, bool) If true, then the Docker container will be + kept running. If false, then as long as the container exists, Terraform + assumes it is successful. +* `ports` - (Optional) See [Ports](#ports) below for details. +* `publish_all_ports` - (Optional, bool) Publish all ports of the container. +* `volumes` - (Optional) See [Volumes](#volumes) below for details. + + +## Ports + +`ports` is a block within the configuration that can be repeated to specify +the port mappings of the container. Each `ports` block supports +the following: + +* `internal` - (Required, int) Port within the container. +* `external` - (Required, int) Port exposed out of the container. +* `ip` - (Optional, string) IP address/mask that can access this port. +* `protocol` - (Optional, string) Protocol that can be used over this port, + defaults to TCP. + + +## Volumes + +`volumes` is a block within the configuration that can be repeated to specify +the volumes attached to a container. Each `volumes` block supports +the following: + +* `from_container` - (Optional, string) The container where the volume is + coming from. +* `container_path` - (Optional, string) The path in the container where the + volume will be mounted. +* `host_path` - (Optional, string) The path on the host where the volume + is coming from. +* `read_only` - (Optinal, bool) If true, this volume will be readonly. + Defaults to false. diff --git a/website/source/docs/providers/docker/r/image.html.markdown b/website/source/docs/providers/docker/r/image.html.markdown new file mode 100644 index 000000000..a2c896110 --- /dev/null +++ b/website/source/docs/providers/docker/r/image.html.markdown @@ -0,0 +1,41 @@ +--- +layout: "docker" +page_title: "Docker: docker_image" +sidebar_current: "docs-docker-resource-image" +description: |- + Downloads and exports the ID of a Docker image. +--- + +# docker\_image + +Downloads and exports the ID of a Docker image. This can be used alongside +[docker\_container](/docs/providers/docker/r/container.html) +to programmatically get the latest image IDs without having to hardcode +them. + +## Example Usage + +``` +# Find the latest Ubuntu precise image. +resource "docker_image" "ubuntu" { + image = "ubuntu:precise" +} + +# Access it somewhere else with ${docker_image.ubuntu.latest} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the Docker image, including any tags. +* `keep_updated` - (Optional) If true, then the Docker image will always + be updated on the host to the latest. If this is false, as long as an + image is downloaded with the correct tag, it won't be redownloaded if + there is a newer image. + +## Attributes Reference + +The following attributes are exported in addition to the above configuration: + +* `latest` (string) - The ID of the image. diff --git a/website/source/layouts/docker.erb b/website/source/layouts/docker.erb new file mode 100644 index 000000000..920e7aa43 --- /dev/null +++ b/website/source/layouts/docker.erb @@ -0,0 +1,30 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> +<% end %> diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index a0b31127a..5c314468c 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -130,7 +130,7 @@ > DigitalOcean - + > DNSMadeEasy @@ -140,6 +140,10 @@ DNSimple + > + Docker + + > Google Cloud From 118a5b9dfdf2e537d698df304fc6a889c0742cea Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 28 Mar 2015 19:06:48 -0700 Subject: [PATCH 136/295] providers/docker: ping docker server on startup --- builtin/providers/docker/provider.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/builtin/providers/docker/provider.go b/builtin/providers/docker/provider.go index 15bc840a4..2fe456e93 100644 --- a/builtin/providers/docker/provider.go +++ b/builtin/providers/docker/provider.go @@ -1,6 +1,8 @@ package docker import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) @@ -38,5 +40,15 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { CertPath: d.Get("cert_path").(string), } - return config.NewClient() + client, err := config.NewClient() + if err != nil { + return nil, fmt.Errorf("Error initializing Docker client: %s", err) + } + + err = client.Ping() + if err != nil { + return nil, fmt.Errorf("Error pinging Docker server: %s", err) + } + + return client, nil } From 755f8d0f4437632cc73bb574d490556875ac1a6c Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Mon, 30 Mar 2015 14:54:03 -0500 Subject: [PATCH 137/295] provider/aws: Fix acceptance test checks for AWS Security Group --- .../aws/resource_aws_security_group_test.go | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/builtin/providers/aws/resource_aws_security_group_test.go b/builtin/providers/aws/resource_aws_security_group_test.go index b1e4e8c82..067cda8a1 100644 --- a/builtin/providers/aws/resource_aws_security_group_test.go +++ b/builtin/providers/aws/resource_aws_security_group_test.go @@ -30,15 +30,15 @@ func TestAccAWSSecurityGroup_normal(t *testing.T) { resource.TestCheckResourceAttr( "aws_security_group.web", "description", "Used in the terraform acceptance tests"), resource.TestCheckResourceAttr( - "aws_security_group.web", "ingress.332851786.protocol", "tcp"), + "aws_security_group.web", "ingress.3629188364.protocol", "tcp"), resource.TestCheckResourceAttr( - "aws_security_group.web", "ingress.332851786.from_port", "80"), + "aws_security_group.web", "ingress.3629188364.from_port", "80"), resource.TestCheckResourceAttr( - "aws_security_group.web", "ingress.332851786.to_port", "8000"), + "aws_security_group.web", "ingress.3629188364.to_port", "8000"), resource.TestCheckResourceAttr( - "aws_security_group.web", "ingress.332851786.cidr_blocks.#", "1"), + "aws_security_group.web", "ingress.3629188364.cidr_blocks.#", "1"), resource.TestCheckResourceAttr( - "aws_security_group.web", "ingress.332851786.cidr_blocks.0", "10.0.0.0/8"), + "aws_security_group.web", "ingress.3629188364.cidr_blocks.0", "10.0.0.0/8"), ), }, }, @@ -116,25 +116,25 @@ func TestAccAWSSecurityGroup_vpc(t *testing.T) { resource.TestCheckResourceAttr( "aws_security_group.web", "description", "Used in the terraform acceptance tests"), resource.TestCheckResourceAttr( - "aws_security_group.web", "ingress.332851786.protocol", "tcp"), + "aws_security_group.web", "ingress.3629188364.protocol", "tcp"), resource.TestCheckResourceAttr( - "aws_security_group.web", "ingress.332851786.from_port", "80"), + "aws_security_group.web", "ingress.3629188364.from_port", "80"), resource.TestCheckResourceAttr( - "aws_security_group.web", "ingress.332851786.to_port", "8000"), + "aws_security_group.web", "ingress.3629188364.to_port", "8000"), resource.TestCheckResourceAttr( - "aws_security_group.web", "ingress.332851786.cidr_blocks.#", "1"), + "aws_security_group.web", "ingress.3629188364.cidr_blocks.#", "1"), resource.TestCheckResourceAttr( - "aws_security_group.web", "ingress.332851786.cidr_blocks.0", "10.0.0.0/8"), + "aws_security_group.web", "ingress.3629188364.cidr_blocks.0", "10.0.0.0/8"), resource.TestCheckResourceAttr( - "aws_security_group.web", "egress.332851786.protocol", "tcp"), + "aws_security_group.web", "egress.3629188364.protocol", "tcp"), resource.TestCheckResourceAttr( - "aws_security_group.web", "egress.332851786.from_port", "80"), + "aws_security_group.web", "egress.3629188364.from_port", "80"), resource.TestCheckResourceAttr( - "aws_security_group.web", "egress.332851786.to_port", "8000"), + "aws_security_group.web", "egress.3629188364.to_port", "8000"), resource.TestCheckResourceAttr( - "aws_security_group.web", "egress.332851786.cidr_blocks.#", "1"), + "aws_security_group.web", "egress.3629188364.cidr_blocks.#", "1"), resource.TestCheckResourceAttr( - "aws_security_group.web", "egress.332851786.cidr_blocks.0", "10.0.0.0/8"), + "aws_security_group.web", "egress.3629188364.cidr_blocks.0", "10.0.0.0/8"), testCheck, ), }, From a6411626bf443abe2d121d43dcaff83b3d948733 Mon Sep 17 00:00:00 2001 From: 7heo <7heo@users.noreply.github.com> Date: Tue, 31 Mar 2015 01:52:39 +0200 Subject: [PATCH 138/295] config: interprets '~' as the current user home dir in file() --- config/interpolate_funcs.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index 8bb76c532..6fa9dd1cb 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/hashicorp/terraform/config/lang/ast" + "github.com/mitchellh/go-homedir" ) // Funcs is the mapping of built-in functions for configuration. @@ -57,7 +58,12 @@ func interpolationFuncFile() ast.Function { ArgTypes: []ast.Type{ast.TypeString}, ReturnType: ast.TypeString, Callback: func(args []interface{}) (interface{}, error) { - data, err := ioutil.ReadFile(args[0].(string)) + var path string + path, err := homedir.Expand(args[0].(string)) + if err != nil { + return "", err + } + data, err := ioutil.ReadFile(path) if err != nil { return "", err } From 8c1fb2a983241338e8e6ebb24d0b0edacd46d5d4 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Mon, 30 Mar 2015 19:01:55 -0500 Subject: [PATCH 139/295] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4209dc0d..2485613f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,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] + * config: Expand `~` with homedir in `file()` paths [GH-1338] * 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. From 766b4902d46891c0ad723ff07c1b142a9b07b530 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Mon, 30 Mar 2015 19:03:01 -0500 Subject: [PATCH 140/295] remove extraneous var declaration just a go nitpick :) --- config/interpolate_funcs.go | 1 - 1 file changed, 1 deletion(-) diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index 6fa9dd1cb..353c45500 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -58,7 +58,6 @@ func interpolationFuncFile() ast.Function { ArgTypes: []ast.Type{ast.TypeString}, ReturnType: ast.TypeString, Callback: func(args []interface{}) (interface{}, error) { - var path string path, err := homedir.Expand(args[0].(string)) if err != nil { return "", err From e485767694574174b316ad6834ade7489463a15d Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 31 Mar 2015 09:41:37 -0500 Subject: [PATCH 141/295] provider/aws: Add non-destructive updates to AWS RDS This introduces non-destructive, in-place upgrades to MultiAZ and Engine Version attributes of AWS RDS instances. --- .../providers/aws/resource_aws_db_instance.go | 41 ++++++++++++++++++- .../providers/aws/r/db_instance.html.markdown | 3 ++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index b690c9a1a..14deff732 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -49,7 +49,6 @@ func resourceAwsDbInstance() *schema.Resource { "engine_version": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: true, }, "storage_encrypted": &schema.Schema{ @@ -121,7 +120,6 @@ func resourceAwsDbInstance() *schema.Resource { Type: schema.TypeBool, Optional: true, Computed: true, - ForceNew: true, }, "port": &schema.Schema{ @@ -189,6 +187,16 @@ func resourceAwsDbInstance() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + // apply_immediately is used to determine when the update modifications + // take place. + // See http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html + "apply_immediately": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "tags": tagsSchema(), }, } @@ -423,6 +431,35 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error conn := meta.(*AWSClient).rdsconn d.Partial(true) + // Change is used to determine if a ModifyDBInstanceMessage request actually + // gets sent. + change := false + + req := &rds.ModifyDBInstanceMessage{ + ApplyImmediately: aws.Boolean(d.Get("apply_immediately").(bool)), + DBInstanceIdentifier: aws.String(d.Id()), + } + + if d.HasChange("engine_version") { + change = true + d.SetPartial("engine_version") + req.EngineVersion = aws.String(d.Get("engine_version").(string)) + } + + if d.HasChange("multi_az") { + change = true + d.SetPartial("multi_az") + req.MultiAZ = aws.Boolean(d.Get("multi_az").(bool)) + } + + if change { + log.Printf("[DEBUG] DB Instance Modification request: %#v", req) + _, err := conn.ModifyDBInstance(req) + if err != nil { + return fmt.Errorf("Error mofigying DB Instance %s: %s", d.Id(), err) + } + } + if arn, err := buildRDSARN(d, meta); err == nil { if err := setTagsRDS(conn, d, arn); err != nil { return err diff --git a/website/source/docs/providers/aws/r/db_instance.html.markdown b/website/source/docs/providers/aws/r/db_instance.html.markdown index 4b2253115..6b3b97552 100644 --- a/website/source/docs/providers/aws/r/db_instance.html.markdown +++ b/website/source/docs/providers/aws/r/db_instance.html.markdown @@ -62,6 +62,9 @@ The following arguments are supported: * `db_subnet_group_name` - (Optional) Name of DB subnet group * `parameter_group_name` - (Optional) Name of the DB parameter group to associate. * `storage_encrypted` - (Optional) Specifies whether the DB instance is encrypted. The Default is `false` if not specified. +* `apply_immediately` - (Optional) Specifies whether any database modifications + are applied immediately, or during the next maintenance window. Default is + `False`. See [Amazon RDS Documentation for more for more information.](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html) ## Attributes Reference From f9fa7480240a30d1f92c5ab4244ec87878f8612e Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 29 Oct 2014 16:52:36 -0500 Subject: [PATCH 142/295] crud for openstack servers v2 --- builtin/bins/provider-openstack/main.go | 12 + builtin/providers/openstack/config.go | 39 +++ builtin/providers/openstack/provider.go | 93 ++++++ .../resource_openstack_compute_instance.go | 297 ++++++++++++++++++ 4 files changed, 441 insertions(+) create mode 100644 builtin/bins/provider-openstack/main.go create mode 100644 builtin/providers/openstack/config.go create mode 100644 builtin/providers/openstack/provider.go create mode 100644 builtin/providers/openstack/resource_openstack_compute_instance.go diff --git a/builtin/bins/provider-openstack/main.go b/builtin/bins/provider-openstack/main.go new file mode 100644 index 000000000..f897f1c55 --- /dev/null +++ b/builtin/bins/provider-openstack/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/openstack" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: openstack.Provider, + }) +} diff --git a/builtin/providers/openstack/config.go b/builtin/providers/openstack/config.go new file mode 100644 index 000000000..cb06a9848 --- /dev/null +++ b/builtin/providers/openstack/config.go @@ -0,0 +1,39 @@ +package openstack + +import ( + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" +) + +type Config struct { + Region string + Username string + Password string + IdentityEndpoint string + TenantName string + + computeV2Client *gophercloud.ServiceClient +} + +func (c *Config) loadAndValidate() error { + ao := gophercloud.AuthOptions{ + Username: c.Username, + Password: c.Password, + IdentityEndpoint: c.IdentityEndpoint, + TenantName: c.TenantName, + } + + client, err := openstack.AuthenticatedClient(ao) + if err != nil { + return err + } + + c.computeV2Client, err = openstack.NewComputeV2(client, gophercloud.EndpointOpts{ + Region: c.Region, + }) + if err != nil { + return err + } + + return nil +} diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go new file mode 100644 index 000000000..a134c3d07 --- /dev/null +++ b/builtin/providers/openstack/provider.go @@ -0,0 +1,93 @@ +package openstack + +import ( + "os" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +// Provider returns a schema.Provider for OpenStack. +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: envDefaultFunc("OS_REGION"), + Description: descriptions["region"], + }, + + "auth_url": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: envDefaultFunc("OS_AUTH_URL"), + Description: descriptions["auth_url"], + }, + + "username": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: envDefaultFunc("OS_USERNAME"), + Description: descriptions["username"], + }, + + "tenant_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: envDefaultFunc("OS_TENANT_NAME"), + //Description: descriptions["tenantname"], + }, + + "password": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: envDefaultFunc("OS_PASSWORD"), + Description: descriptions["password"], + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "openstack_compute_instance": resourceComputeInstance(), + }, + + ConfigureFunc: configureProvider, + } +} + +func configureProvider(d *schema.ResourceData) (interface{}, error) { + config := Config{ + Region: d.Get("region").(string), + IdentityEndpoint: d.Get("auth_url").(string), + Username: d.Get("username").(string), + Password: d.Get("password").(string), + TenantName: d.Get("tenant_name").(string), + } + + if err := config.loadAndValidate(); err != nil { + return nil, err + } + + return &config, nil +} + +func envDefaultFunc(k string) schema.SchemaDefaultFunc { + return func() (interface{}, error) { + if v := os.Getenv(k); v != "" { + return v, nil + } + + return nil, nil + } +} + +var descriptions map[string]string + +func init() { + descriptions = map[string]string{ + "region": "The region where OpenStack operations will take place.", + "auth_url": "The endpoint against which to authenticate.", + "username": "The username with which to authenticate.", + "password": "The password with which to authenticate.", + } +} diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance.go new file mode 100644 index 000000000..07bf040e9 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_compute_instance.go @@ -0,0 +1,297 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" +) + +func resourceComputeInstance() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeInstanceCreate, + Read: resourceComputeInstanceRead, + Update: resourceComputeInstanceUpdate, + Delete: resourceComputeInstanceDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "image_ref": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "flavor_ref": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "security_groups": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: false, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + + "availability_zone": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "networks": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "uuid": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "port": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "fixed_ip": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + + "metadata": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + }, + + "config_drive": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "access_ip_v4": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Optional: true, + ForceNew: false, + }, + + "access_ip_v6": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Optional: true, + ForceNew: false, + }, + }, + } +} + +func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + createOpts := &servers.CreateOpts{ + Name: d.Get("name").(string), + ImageRef: d.Get("image_ref").(string), + FlavorRef: d.Get("flavor_ref").(string), + //SecurityGroups []string + AvailabilityZone: d.Get("availability_zone").(string), + Networks: resourceInstanceNetworks(d), + Metadata: resourceInstanceMetadata(d), + ConfigDrive: d.Get("config_drive").(bool), + } + + log.Printf("[INFO] Requesting instance creation") + server, err := servers.Create(osClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack server: %s", err) + } + log.Printf("[INFO] Instance ID: %s", server.ID) + + // Store the ID now + d.SetId(server.ID) + + // Wait for the instance to become running so we can get some attributes + // that aren't available until later. + log.Printf( + "[DEBUG] Waiting for instance (%s) to become running", + server.ID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"BUILD"}, + Target: "ACTIVE", + Refresh: ServerStateRefreshFunc(osClient, server.ID), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + serverRaw, err := stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for instance (%s) to become ready: %s", + server.ID, err) + } + + server = serverRaw.(*servers.Server) + + return resourceComputeInstanceRead(d, meta) +} + +func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + server, err := servers.Get(osClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error retrieving OpenStack server: %s", err) + } + + log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server) + + d.Set("name", server.Name) + d.Set("access_ip_v4", server.AccessIPv4) + d.Set("access_ip_v6", server.AccessIPv6) + + host := server.AccessIPv4 + if host == "" { + if publicAddressesRaw, ok := server.Addresses["public"]; ok { + publicAddresses := publicAddressesRaw.([]interface{}) + for _, paRaw := range publicAddresses { + pa := paRaw.(map[string]interface{}) + if pa["version"].(float64) == 4 { + host = pa["addr"].(string) + } + } + } + } + + log.Printf("host: %s", host) + + // Initialize the connection info + d.SetConnInfo(map[string]string{ + "type": "ssh", + "host": host, + }) + + d.Set("metadata", server.Metadata) + + return nil +} + +func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + var updateOpts servers.UpdateOpts + // If the Metadata has changed, then update that. + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("access_ip_v4") { + updateOpts.AccessIPv4 = d.Get("access_ip_v4").(string) + } + if d.HasChange("access_ip_v6") { + updateOpts.AccessIPv4 = d.Get("access_ip_v6").(string) + } + + // If there's nothing to update, don't waste an HTTP call. + if updateOpts != (servers.UpdateOpts{}) { + log.Printf("[DEBUG] Updating Server %s with options: %+v", d.Id(), updateOpts) + + _, err := servers.Update(osClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating Openstack server: %s", err) + } + } + + return resourceComputeInstanceRead(d, meta) +} + +func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + err := servers.Delete(osClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack server: %s", err) + } + + // Wait for the instance to delete before moving on. + log.Printf( + "[DEBUG] Waiting for instance (%s) to delete", + d.Id()) + + stateConf := &resource.StateChangeConf{ + Target: "", + Refresh: ServerStateRefreshFunc(osClient, d.Id()), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for instance (%s) to delete: %s", + d.Id(), err) + } + + d.SetId("") + return nil +} + +// ServerStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// an OpenStack instance. +func ServerStateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + s, err := servers.Get(client, instanceID).Extract() + if err != nil { + return nil, "", err + } + + return s, s.Status, nil + } +} + +func resourceInstanceNetworks(d *schema.ResourceData) []servers.Network { + rawNetworks := d.Get("networks").([]interface{}) + networks := make([]servers.Network, len(rawNetworks)) + for i, raw := range rawNetworks { + rawMap := raw.(map[string]interface{}) + networks[i] = servers.Network{ + UUID: rawMap["uuid"].(string), + Port: rawMap["port"].(string), + FixedIP: rawMap["fixed_ip"].(string), + } + } + return networks +} + +func resourceInstanceMetadata(d *schema.ResourceData) map[string]string { + m := make(map[string]string) + for key, val := range d.Get("metadata").(map[string]interface{}) { + m[key] = val.(string) + } + return m +} From cc9ee787ac8402ac0f44f1fbfc8d20ccd9878999 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Fri, 2 Jan 2015 12:40:42 -0700 Subject: [PATCH 143/295] update openstack server metadata --- .../resource_openstack_compute_instance.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance.go index 07bf040e9..e4d1407d7 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance.go @@ -221,7 +221,21 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err _, err := servers.Update(osClient, d.Id(), updateOpts).Extract() if err != nil { - return fmt.Errorf("Error updating Openstack server: %s", err) + return fmt.Errorf("Error updating OpenStack server: %s", err) + } + } + + if d.HasChange("metadata") { + var metadataOpts servers.MetadataOpts + metadataOpts = make(servers.MetadataOpts) + newMetadata := d.Get("metadata").(map[string]interface{}) + for k, v := range newMetadata { + metadataOpts[k] = v.(string) + } + + _, err := servers.UpdateMetadata(osClient, d.Id(), metadataOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack server (%s) metadata: %s", d.Id(), err) } } From 3112103acf15c392565838496a187c2499b3be28 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 4 Jan 2015 09:52:49 -0700 Subject: [PATCH 144/295] server keypair --- .../resource_openstack_compute_instance.go | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance.go index e4d1407d7..54dbe1b67 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" ) @@ -81,6 +82,7 @@ func resourceComputeInstance() *schema.Resource { "metadata": &schema.Schema{ Type: schema.TypeMap, Optional: true, + ForceNew: false, }, "config_drive": &schema.Schema{ @@ -102,6 +104,12 @@ func resourceComputeInstance() *schema.Resource { Optional: true, ForceNew: false, }, + + "key_pair": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, }, } } @@ -110,7 +118,9 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err config := meta.(*Config) osClient := config.computeV2Client - createOpts := &servers.CreateOpts{ + var createOpts servers.CreateOptsBuilder + + serverCreateOpts := &servers.CreateOpts{ Name: d.Get("name").(string), ImageRef: d.Get("image_ref").(string), FlavorRef: d.Get("flavor_ref").(string), @@ -121,6 +131,15 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err ConfigDrive: d.Get("config_drive").(bool), } + if kp, ok := d.Get("key_pair").(map[string]interface{}); ok && kp != nil { + if keyName, ok := kp["name"].(string); ok && keyName != "" { + createOpts = &keypairs.CreateOptsExt{ + serverCreateOpts, + keyName, + } + } + } + log.Printf("[INFO] Requesting instance creation") server, err := servers.Create(osClient, createOpts).Extract() if err != nil { From 48e92f8173063cb8214eef4a3175c547ad1965a1 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 4 Jan 2015 15:26:57 -0700 Subject: [PATCH 145/295] OS_REGION -> OS_REGION_NAME (thank you @hartzell) --- builtin/providers/openstack/provider.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index a134c3d07..356081d19 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -14,7 +14,7 @@ func Provider() terraform.ResourceProvider { "region": &schema.Schema{ Type: schema.TypeString, Required: true, - DefaultFunc: envDefaultFunc("OS_REGION"), + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), Description: descriptions["region"], }, @@ -61,7 +61,7 @@ func configureProvider(d *schema.ResourceData) (interface{}, error) { IdentityEndpoint: d.Get("auth_url").(string), Username: d.Get("username").(string), Password: d.Get("password").(string), - TenantName: d.Get("tenant_name").(string), + TenantName: d.Get("tenant_name").(string), } if err := config.loadAndValidate(); err != nil { From 0bb0dad58ce628b3030ea07c8ed3cfd3fdec2c4f Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 4 Jan 2015 15:27:54 -0700 Subject: [PATCH 146/295] provider test --- builtin/providers/openstack/provider_test.go | 51 ++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 builtin/providers/openstack/provider_test.go diff --git a/builtin/providers/openstack/provider_test.go b/builtin/providers/openstack/provider_test.go new file mode 100644 index 000000000..2819a4575 --- /dev/null +++ b/builtin/providers/openstack/provider_test.go @@ -0,0 +1,51 @@ +package openstack + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +var testAccProviders map[string]terraform.ResourceProvider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider().(*schema.Provider) + testAccProviders = map[string]terraform.ResourceProvider{ + "openstack": testAccProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvider_impl(t *testing.T) { + var _ terraform.ResourceProvider = Provider() +} + +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("OS_REGION_NAME"); v == "" { + t.Fatal("OS_REGION_NAME must be set for acceptance tests") + } + + if v := os.Getenv("OS_AUTH_URL"); v == "" { + t.Fatal("OS_AUTH_URL must be set for acceptance tests") + } + + if v := os.Getenv("OS_USERNAME"); v == "" { + t.Fatal("OS_USERNAME must be set for acceptance tests") + } + + if v := os.Getenv("OS_TENANT_NAME"); v != "us-central1" { + t.Fatal("OS_TENANT_NAME must be set to us-central1 for acceptance tests") + } + + if v := os.Getenv("OS_PASSWORD"); v != "us-central1" { + t.Fatal("OS_PASSWORD must be set to us-central1 for acceptance tests") + } +} From f17649e9dc1a60a357c9e22ae9bc2c0617e73a20 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 5 Jan 2015 10:05:25 -0700 Subject: [PATCH 147/295] server resizing --- .../resource_openstack_compute_instance.go | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance.go index 54dbe1b67..9075a08ae 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance.go @@ -214,6 +214,11 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error }) d.Set("metadata", server.Metadata) + newFlavor, ok := server.Flavor["id"].(string) + if !ok { + return fmt.Errorf("Error setting OpenStack server's flavor: %v", newFlavor) + } + d.Set("flavor_ref", newFlavor) return nil } @@ -258,6 +263,54 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } } + if d.HasChange("flavor_ref") { + resizeOpts := &servers.ResizeOpts{ + FlavorRef: d.Get("flavor_ref").(string), + } + err := servers.Resize(osClient, d.Id(), resizeOpts).ExtractErr() + if err != nil { + return fmt.Errorf("Error resizing OpenStack server: %s", err) + } + + // Wait for the instance to finish resizing. + log.Printf("[DEBUG] Waiting for instance (%s) to finish resizing", d.Id()) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"RESIZE"}, + Target: "VERIFY_RESIZE", + Refresh: ServerStateRefreshFunc(osClient, d.Id()), + Timeout: 3 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err) + } + + // Confirm resize. + log.Printf("[DEBUG] Confirming resize") + err = servers.ConfirmResize(osClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error confirming resize of OpenStack server: %s", err) + } + + stateConf = &resource.StateChangeConf{ + Pending: []string{"VERIFY_RESIZE"}, + Target: "ACTIVE", + Refresh: ServerStateRefreshFunc(osClient, d.Id()), + Timeout: 3 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err) + } + } + return resourceComputeInstanceRead(d, meta) } @@ -271,9 +324,7 @@ func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) err } // Wait for the instance to delete before moving on. - log.Printf( - "[DEBUG] Waiting for instance (%s) to delete", - d.Id()) + log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id()) stateConf := &resource.StateChangeConf{ Target: "", From 04a9d47bcab1bd9fb6a1bf7d951deafdc3f06c5c Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 5 Jan 2015 12:16:33 -0700 Subject: [PATCH 148/295] add/update/remove sec groups from server --- .../resource_openstack_compute_instance.go | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance.go index 9075a08ae..57afbb309 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance.go @@ -10,7 +10,9 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" + "github.com/rackspace/gophercloud/pagination" ) func resourceComputeInstance() *schema.Resource { @@ -121,10 +123,10 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err var createOpts servers.CreateOptsBuilder serverCreateOpts := &servers.CreateOpts{ - Name: d.Get("name").(string), - ImageRef: d.Get("image_ref").(string), - FlavorRef: d.Get("flavor_ref").(string), - //SecurityGroups []string + Name: d.Get("name").(string), + ImageRef: d.Get("image_ref").(string), + FlavorRef: d.Get("flavor_ref").(string), + SecurityGroups: resourceInstanceSecGroups(d), AvailabilityZone: d.Get("availability_zone").(string), Networks: resourceInstanceNetworks(d), Metadata: resourceInstanceMetadata(d), @@ -214,6 +216,22 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error }) d.Set("metadata", server.Metadata) + + var currentSG []string + err = secgroups.ListByServer(osClient, d.Id()).EachPage(func(page pagination.Page) (bool, error) { + secGrpList, err := secgroups.ExtractSecurityGroups(page) + if err != nil { + return false, fmt.Errorf("Error setting security groups for OpenStack server: %s", err) + } + + for _, sg := range secGrpList { + currentSG = append(currentSG, sg.Name) + } + + return true, nil + }) + d.Set("security_groups", currentSG) + newFlavor, ok := server.Flavor["id"].(string) if !ok { return fmt.Errorf("Error setting OpenStack server's flavor: %v", newFlavor) @@ -263,6 +281,33 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } } + if d.HasChange("security_groups") { + oldSGRaw, newSGRaw := d.GetChange("security_groups") + oldSGSet, newSGSet := oldSGRaw.(*schema.Set), newSGRaw.(*schema.Set) + secgroupsToAdd := newSGSet.Difference(oldSGSet) + secgroupsToRemove := oldSGSet.Difference(newSGSet) + + log.Printf("[DEBUG] Security groups to add: %v", secgroupsToAdd) + + log.Printf("[DEBUG] Security groups to remove: %v", secgroupsToRemove) + + for _, g := range secgroupsToAdd.List() { + err := secgroups.AddServerToGroup(osClient, d.Id(), g.(string)).ExtractErr() + if err != nil { + return fmt.Errorf("Error adding security group to OpenStack server (%s): %s", d.Id(), err) + } + log.Printf("[DEBUG] Added security group (%s) to instance (%s)", g.(string), d.Id()) + } + + for _, g := range secgroupsToRemove.List() { + err := secgroups.RemoveServerFromGroup(osClient, d.Id(), g.(string)).ExtractErr() + if err != nil { + return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) + } + log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g.(string), d.Id()) + } + } + if d.HasChange("flavor_ref") { resizeOpts := &servers.ResizeOpts{ FlavorRef: d.Get("flavor_ref").(string), @@ -358,6 +403,15 @@ func ServerStateRefreshFunc(client *gophercloud.ServiceClient, instanceID string } } +func resourceInstanceSecGroups(d *schema.ResourceData) []string { + rawSecGroups := d.Get("security_groups").(*schema.Set) + secgroups := make([]string, rawSecGroups.Len()) + for i, raw := range rawSecGroups.List() { + secgroups[i] = raw.(string) + } + return secgroups +} + func resourceInstanceNetworks(d *schema.ResourceData) []servers.Network { rawNetworks := d.Get("networks").([]interface{}) networks := make([]servers.Network, len(rawNetworks)) From edc280a8dc6ef702a34880502ec41d5852a4bab4 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 7 Jan 2015 11:38:23 -0700 Subject: [PATCH 149/295] add/update admin pass on server; change key pair format from map to string --- .../resource_openstack_compute_instance.go | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance.go index 57afbb309..a57fda13b 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance.go @@ -93,6 +93,12 @@ func resourceComputeInstance() *schema.Resource { ForceNew: true, }, + "admin_pass": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "access_ip_v4": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -108,7 +114,7 @@ func resourceComputeInstance() *schema.Resource { }, "key_pair": &schema.Schema{ - Type: schema.TypeMap, + Type: schema.TypeString, Optional: true, ForceNew: true, }, @@ -131,14 +137,13 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err Networks: resourceInstanceNetworks(d), Metadata: resourceInstanceMetadata(d), ConfigDrive: d.Get("config_drive").(bool), + AdminPass: d.Get("admin_pass").(string), } - if kp, ok := d.Get("key_pair").(map[string]interface{}); ok && kp != nil { - if keyName, ok := kp["name"].(string); ok && keyName != "" { - createOpts = &keypairs.CreateOptsExt{ - serverCreateOpts, - keyName, - } + if keyName, ok := d.Get("key_pair").(string); ok && keyName != "" { + createOpts = &keypairs.CreateOptsExt{ + serverCreateOpts, + keyName, } } @@ -308,6 +313,15 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } } + if d.HasChange("admin_pass") { + if newPwd, ok := d.Get("admin_pass").(string); ok { + err := servers.ChangeAdminPassword(osClient, d.Id(), newPwd).ExtractErr() + if err != nil { + return fmt.Errorf("Error changing admin password of OpenStack server (%s): %s", d.Id(), err) + } + } + } + if d.HasChange("flavor_ref") { resizeOpts := &servers.ResizeOpts{ FlavorRef: d.Get("flavor_ref").(string), From 686cd4b02b683575d6609609e14c56bb7b5dce31 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 7 Jan 2015 12:19:30 -0700 Subject: [PATCH 150/295] add OpenStack to list of IaaS providers --- website/source/docs/providers/index.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/providers/index.html.markdown b/website/source/docs/providers/index.html.markdown index f03c17c54..5365d0e86 100644 --- a/website/source/docs/providers/index.html.markdown +++ b/website/source/docs/providers/index.html.markdown @@ -14,7 +14,7 @@ etc. Almost any infrastructure noun can be represented as a resource in Terrafor Terraform is agnostic to the underlying platforms by supporting providers. A provider is responsible for understanding API interactions and exposing resources. Providers -generally are an IaaS (e.g. AWS, DigitalOcean, GCE), PaaS (e.g. Heroku, CloudFoundry), +generally are an IaaS (e.g. AWS, DigitalOcean, GCE, OpenStack), PaaS (e.g. Heroku, CloudFoundry), or SaaS services (e.g. Atlas, DNSimple, CloudFlare). Use the navigation to the left to read about the available providers. From 47955b1d446e70e27b72f77f4dffe0dc15c40796 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 7 Jan 2015 16:45:06 -0700 Subject: [PATCH 151/295] remove unneeded variables during server creation --- .../openstack/resource_openstack_compute_instance.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance.go index a57fda13b..7ba4199fc 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance.go @@ -172,15 +172,13 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err MinTimeout: 3 * time.Second, } - serverRaw, err := stateConf.WaitForState() + _, err = stateConf.WaitForState() if err != nil { return fmt.Errorf( "Error waiting for instance (%s) to become ready: %s", server.ID, err) } - server = serverRaw.(*servers.Server) - return resourceComputeInstanceRead(d, meta) } From 01e41646d3796a04d35bba8312505b357938c33b Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 7 Jan 2015 16:45:57 -0700 Subject: [PATCH 152/295] add/get/delete keypairs --- builtin/providers/openstack/provider.go | 1 + .../resource_openstack_compute_keypair.go | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_compute_keypair.go diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 356081d19..2a5aad71b 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -49,6 +49,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "openstack_compute_instance": resourceComputeInstance(), + "openstack_compute_keypair": resourceComputeKeypair(), }, ConfigureFunc: configureProvider, diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair.go b/builtin/providers/openstack/resource_openstack_compute_keypair.go new file mode 100644 index 000000000..a8ca16e08 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_compute_keypair.go @@ -0,0 +1,76 @@ +package openstack + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" +) + +func resourceComputeKeypair() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeKeypairCreate, + Read: resourceComputeKeypairRead, + Delete: resourceComputeKeypairDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "public_key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceComputeKeypairCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + createOpts := keypairs.CreateOpts{ + Name: d.Get("name").(string), + PublicKey: d.Get("public_key").(string), + } + + kp, err := keypairs.Create(osClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack keypair: %s", err) + } + + d.SetId(kp.Name) + + return resourceComputeKeypairRead(d, meta) +} + +func resourceComputeKeypairRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + kp, err := keypairs.Get(osClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error retrieving OpenStack keypair: %s", err) + } + + d.Set("name", kp.Name) + d.Set("public_key", kp.PublicKey) + + return nil +} + +func resourceComputeKeypairDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + err := keypairs.Delete(osClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack keypair: %s", err) + } + d.SetId("") + return nil +} From 00ee96fb6fe4bd7289dd7f052ffbfee810ea49c2 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 7 Jan 2015 17:09:25 -0700 Subject: [PATCH 153/295] openstack servers and keypairs docs --- .../providers/openstack/index.html.markdown | 47 +++++++++++ .../r/compute_instance.html.markdown | 80 +++++++++++++++++++ .../openstack/r/compute_keypair.html.markdown | 37 +++++++++ website/source/layouts/openstack.erb | 29 +++++++ 4 files changed, 193 insertions(+) create mode 100644 website/source/docs/providers/openstack/index.html.markdown create mode 100644 website/source/docs/providers/openstack/r/compute_instance.html.markdown create mode 100644 website/source/docs/providers/openstack/r/compute_keypair.html.markdown create mode 100644 website/source/layouts/openstack.erb diff --git a/website/source/docs/providers/openstack/index.html.markdown b/website/source/docs/providers/openstack/index.html.markdown new file mode 100644 index 000000000..652523c5b --- /dev/null +++ b/website/source/docs/providers/openstack/index.html.markdown @@ -0,0 +1,47 @@ +--- +layout: "openstack" +page_title: "Provider: OpenStack" +sidebar_current: "docs-openstack-index" +description: |- + The OpenStack provider is used to interact with the many resources supported by OpenStack. The provider needs to be configured with the proper credentials before it can be used. +--- + +# OpenStack Provider + +The OpenStack provider is used to interact with the +many resources supported by OpenStack. The provider needs to be configured +with the proper credentials before it can be used. + +Use the navigation to the left to read about the available resources. + +## Example Usage + +``` +# Configure the OpenStack Provider +provider "openstack" { + username = "admin" + tenant_name = "admin" + password = "pwd" + auth_url = "http://myauthurl:5000/v2.0" + region = "RegionOne" +} + +# Create a web server +resource "openstack_compute_instance" "test-server" { + ... +} +``` + +## Configuration Reference + +The following arguments are supported: + +* `username` - (Required) + +* `tenant_name` - (Required) + +* `password` - (Required) + +* `auth_url` - (Required) + +* `region` - (Required) diff --git a/website/source/docs/providers/openstack/r/compute_instance.html.markdown b/website/source/docs/providers/openstack/r/compute_instance.html.markdown new file mode 100644 index 000000000..f22624bf9 --- /dev/null +++ b/website/source/docs/providers/openstack/r/compute_instance.html.markdown @@ -0,0 +1,80 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_compute_instance" +sidebar_current: "docs-openstack-resource-compute-instance" +description: |- + Manages a VM instance resource within OpenStack. +--- + +# openstack\_compute\_instance + +Manages a VM instance resource within OpenStack. + +## Example Usage + +``` +resource "openstack_compute_instance" "test-server" { + name = "tf-test" + image_ref = "ad091b52-742f-469e-8f3c-fd81cadf0743" + flavor_ref = "3" + metadata { + this = "that" + } + key_pair = "my_key_pair_name" + security_groups = ["test-group-1"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) A unique name for the resource. + +* `image_ref` - (Required) The image reference (ID) for the desired image for + the server. Changing this creates a new server. + +* `flavor_ref` - (Required) The flavor reference (ID) for the desired flavor + for the server. Changing this resizes the existing server. + +* `security_groups` - (Optional) An array of one or more security group names + to associate with the server. Changing this results in adding/removing + security groups from the existing server. + +* `availability_zone` - (Optional) The availability zone in which to create + the server. Changing this creates a new server. + +* `networks` - (Optional) An array of one or more networks to attach to the + instance. The network object structure is documented below. + +* `metadata` - (Optional) Metadata key/value pairs to make available from + within the instance. Changing this updates the existing server metadata. + +* `admin_pass` - (Optional) The administrative password to assign to the server. + Changing this changes the root password on the existing server. + +* `key_pair` - (Optional) The name of a key pair to put on the server. The key + pair must already be created and associated with the tenant's account. + Changing this creates a new server. + +The `network` block supports: + +* `uuid` - (Required unless `port` is provided) The network UUID to attach to + the server. + +* `port` - (Required unless `uuid` is provided) The port UUID of a network to + attach to the server. + +* `fixed_ip` - (Optional) Specifies a fixed IP address to be used on this + network. + +## Attributes Reference + +The following attributes are exported: + +* `name` - See Argument Reference above. +* `access_ip_v4` - See Argument Reference above. +* `access_ip_v6` - See Argument Reference above. +* `metadata` - See Argument Reference above. +* `security_groups` - See Argument Reference above. +* `flavor_ref` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/compute_keypair.html.markdown b/website/source/docs/providers/openstack/r/compute_keypair.html.markdown new file mode 100644 index 000000000..15459a0ec --- /dev/null +++ b/website/source/docs/providers/openstack/r/compute_keypair.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_compute_keypair" +sidebar_current: "docs-openstack-resource-compute-keypair" +description: |- + Manages a keypair resource within OpenStack. +--- + +# openstack\_compute\_keypair + +Manages a keypair resource within OpenStack. + +## Example Usage + +``` +resource "openstack_compute_keypair" "test-keypair" { + name = "my-keypair" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAjpC1hwiOCCmKEWxJ4qzTTsJbKzndLotBCz5PcwtUnflmU+gHJtWMZKpuEGVi29h0A/+ydKek1O18k10Ff+4tyFjiHDQAnOfgWf7+b1yK+qDip3X1C0UPMbwHlTfSGWLGZqd9LvEFx9k3h/M+VtMvwR1lJ9LUyTAImnNjWG7TaIPmui30HvM2UiFEmqkr4ijq45MyX2+fLIePLRIF61p4whjHAQYufqyno3BS48icQb4p6iVEZPo4AE2o9oIyQvj2mx4dk5Y8CgSETOZTYDOR3rU2fZTRDRgPJDH9FWvQjF5tA0p3d9CoWWd2s6GKKbfoUIi8R/Db1BSPJwkqB" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) A unique name for the keypair. Changing this creates a new + keypair. + +* `public_key` - (Required) A pregenerated OpenSSH-formatted public key. + Changing this creates a new keypair. + +## Attributes Reference + +The following attributes are exported: + +* `name` - See Argument Reference above. +* `public_key` - See Argument Reference above. diff --git a/website/source/layouts/openstack.erb b/website/source/layouts/openstack.erb new file mode 100644 index 000000000..7547a3b09 --- /dev/null +++ b/website/source/layouts/openstack.erb @@ -0,0 +1,29 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> + <% end %> From 23d425072ca0f9759e423a9f5b486848430a746d Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Thu, 8 Jan 2015 10:47:10 -0700 Subject: [PATCH 154/295] add/delete security groups and rules --- builtin/providers/openstack/provider.go | 6 +- .../openstack/resource_compute_secgroup.go | 103 ++++++++++++++++++ .../resource_compute_secgrouprule.go | 99 +++++++++++++++++ 3 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 builtin/providers/openstack/resource_compute_secgroup.go create mode 100644 builtin/providers/openstack/resource_compute_secgrouprule.go diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 2a5aad71b..b9bb5d29b 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -48,8 +48,10 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "openstack_compute_instance": resourceComputeInstance(), - "openstack_compute_keypair": resourceComputeKeypair(), + "openstack_compute_instance": resourceComputeInstance(), + "openstack_compute_keypair": resourceComputeKeypair(), + "openstack_compute_secgroup": resourceComputeSecGroup(), + "openstack_compute_secgrouprule": resourceComputeSecGroupRule(), }, ConfigureFunc: configureProvider, diff --git a/builtin/providers/openstack/resource_compute_secgroup.go b/builtin/providers/openstack/resource_compute_secgroup.go new file mode 100644 index 000000000..f6471eb81 --- /dev/null +++ b/builtin/providers/openstack/resource_compute_secgroup.go @@ -0,0 +1,103 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" +) + +func resourceComputeSecGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeSecGroupCreate, + Read: resourceComputeSecGroupRead, + Update: resourceComputeSecGroupUpdate, + Delete: resourceComputeSecGroupDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + }, + } +} + +func resourceComputeSecGroupCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + createOpts := secgroups.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + } + + sg, err := secgroups.Create(osClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack security group: %s", err) + } + + d.SetId(sg.ID) + + return resourceComputeSecGroupRead(d, meta) +} + +func resourceComputeSecGroupRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + sg, err := secgroups.Get(osClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error retrieving OpenStack security group: %s", err) + } + + d.Set("name", sg.Name) + d.Set("description", sg.Description) + + return nil +} + +func resourceComputeSecGroupUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + var updateOpts secgroups.UpdateOpts + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("description") { + updateOpts.Description = d.Get("description").(string) + } + + // If there's nothing to update, don't waste an HTTP call. + if updateOpts != (secgroups.UpdateOpts{}) { + log.Printf("[DEBUG] Updating Security Group (%s) with options: %+v", d.Id(), updateOpts) + + _, err := secgroups.Update(osClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack security group (%s): %s", d.Id(), err) + } + } + + return resourceComputeSecGroupRead(d, meta) +} + +func resourceComputeSecGroupDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + err := secgroups.Delete(osClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack security group: %s", err) + } + d.SetId("") + return nil +} diff --git a/builtin/providers/openstack/resource_compute_secgrouprule.go b/builtin/providers/openstack/resource_compute_secgrouprule.go new file mode 100644 index 000000000..1063e2df0 --- /dev/null +++ b/builtin/providers/openstack/resource_compute_secgrouprule.go @@ -0,0 +1,99 @@ +package openstack + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" +) + +func resourceComputeSecGroupRule() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeSecGroupRuleCreate, + Read: resourceComputeSecGroupRuleRead, + Delete: resourceComputeSecGroupRuleDelete, + + Schema: map[string]*schema.Schema{ + "group_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "from_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "to_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "ip_protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "cidr": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "from_group_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceComputeSecGroupRuleCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + createOpts := secgroups.CreateRuleOpts{ + ParentGroupID: d.Get("group_id").(string), + FromPort: d.Get("from_port").(int), + ToPort: d.Get("to_port").(int), + IPProtocol: d.Get("ip_protocol").(string), + CIDR: d.Get("cidr").(string), + FromGroupID: d.Get("from_group_id").(string), + } + + sgr, err := secgroups.CreateRule(osClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack security group rule: %s", err) + } + + d.SetId(sgr.ID) + d.Set("group_id", sgr.ParentGroupID) + d.Set("from_port", sgr.FromPort) + d.Set("to_port", sgr.ToPort) + d.Set("ip_protocol", sgr.IPProtocol) + d.Set("cidr", sgr.IPRange.CIDR) + d.Set("from_group_id", d.Get("from_group_id").(string)) + + return resourceComputeSecGroupRuleRead(d, meta) +} + +func resourceComputeSecGroupRuleRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceComputeSecGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.computeV2Client + + err := secgroups.DeleteRule(osClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack security group rule: %s", err) + } + d.SetId("") + return nil +} From d7560de2ddf46b6d587284718a7e7fb934770865 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Thu, 8 Jan 2015 10:48:00 -0700 Subject: [PATCH 155/295] remove errant comment --- .../providers/openstack/resource_openstack_compute_instance.go | 1 - 1 file changed, 1 deletion(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance.go index 7ba4199fc..e8aa71848 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance.go @@ -249,7 +249,6 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err osClient := config.computeV2Client var updateOpts servers.UpdateOpts - // If the Metadata has changed, then update that. if d.HasChange("name") { updateOpts.Name = d.Get("name").(string) } From fc344e90604a2639b31415c963a8c73eed171d59 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Thu, 8 Jan 2015 14:11:58 -0700 Subject: [PATCH 156/295] sec group and sec group rule docs --- .../r/compute_secgroup.html.markdown | 37 +++++++++++ .../r/compute_secgrouprule.html.markdown | 63 +++++++++++++++++++ website/source/layouts/openstack.erb | 6 ++ 3 files changed, 106 insertions(+) create mode 100644 website/source/docs/providers/openstack/r/compute_secgroup.html.markdown create mode 100644 website/source/docs/providers/openstack/r/compute_secgrouprule.html.markdown diff --git a/website/source/docs/providers/openstack/r/compute_secgroup.html.markdown b/website/source/docs/providers/openstack/r/compute_secgroup.html.markdown new file mode 100644 index 000000000..27045b62b --- /dev/null +++ b/website/source/docs/providers/openstack/r/compute_secgroup.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_compute_secgroup" +sidebar_current: "docs-openstack-resource-compute-secgroup" +description: |- + Manages a security group resource within OpenStack. +--- + +# openstack\_compute\_secgroup + +Manages a security group resource within OpenStack. + +## Example Usage + +``` +resource "openstack_compute_secgroup" "secgroup_1" { + name = "my_secgroup" + description = "my security group" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) A unique name for the security group. Changing this + updates the `name` of an existing security group. + +* `description` - (Required) A description for the security group. Changing this + updates the `description` of an existing security group. + +## Attributes Reference + +The following attributes are exported: + +* `name` - See Argument Reference above. +* `description` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/compute_secgrouprule.html.markdown b/website/source/docs/providers/openstack/r/compute_secgrouprule.html.markdown new file mode 100644 index 000000000..95b92981c --- /dev/null +++ b/website/source/docs/providers/openstack/r/compute_secgrouprule.html.markdown @@ -0,0 +1,63 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_compute_secgrouprule" +sidebar_current: "docs-openstack-resource-compute-secgrouprule" +description: |- + Manages a security group rule resource within OpenStack. +--- + +# openstack\_compute\_secgrouprule + +Manages a security group rule resource within OpenStack. + +## Example Usage + +``` +resource "openstack_compute_secgroup" "secgroup_1" { + name = "my_secgroup" + description = "my security group" +} + +resource "openstack_compute_secgrouprule" "secgrouprule_1" { + group_id = "${openstack_compute_secgroup.secgroup_1.id}" + from_port = 22 + to_port = 22 + ip_protocol = "TCP" + cidr = "0.0.0.0/0" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `group_id` - (Required) The ID of the group to which this rule will be added. + Changing this creates a new security group rule. + +* `from_port` - (Required) An integer representing the lower bound of the port + range to open. Changing this creates a new security group rule. + +* `to_port` - (Required) An integer representing the upper bound of the port + range to open. Changing this creates a new security group rule. + +* `ip_protocol` - (Required) The protocol type that will be allowed. Changing + this creates a new security group rule. + +* `cidr` - (Optional) Required is `from_group_id` is empty. The IP range that + will be the source of network traffic to the security group. Use 0.0.0.0./0 + to allow all IP addresses. Changing this creates a new security group rule. + +* `from_group_id - (Optional) Required is `cidr` is empty. The ID of a group + from which to forward traffic to the parent group. Changing + this creates a new security group rule. + +## Attributes Reference + +The following attributes are exported: + +* `group_id` - See Argument Reference above. +* `from_port` - See Argument Reference above. +* `to_port` - See Argument Reference above. +* `ip_protocol` - See Argument Reference above. +* `cidr` - See Argument Reference above. +* `from_group_id` - See Argument Reference above. diff --git a/website/source/layouts/openstack.erb b/website/source/layouts/openstack.erb index 7547a3b09..69bc4ba2e 100644 --- a/website/source/layouts/openstack.erb +++ b/website/source/layouts/openstack.erb @@ -19,6 +19,12 @@ > openstack_compute_keypair + > + openstack_compute_keypair + + > + openstack_compute_keypair + From 457bbe661a92c0eee531a659979944373e03ff48 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 10 Jan 2015 14:58:41 -0700 Subject: [PATCH 157/295] resource_compute_secgroup -> resource_openstack_compute_secgroup --- ...compute_secgroup.go => resource_openstack_compute_secgroup.go} | 0 ...secgrouprule.go => resource_openstack_compute_secgrouprule.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename builtin/providers/openstack/{resource_compute_secgroup.go => resource_openstack_compute_secgroup.go} (100%) rename builtin/providers/openstack/{resource_compute_secgrouprule.go => resource_openstack_compute_secgrouprule.go} (100%) diff --git a/builtin/providers/openstack/resource_compute_secgroup.go b/builtin/providers/openstack/resource_openstack_compute_secgroup.go similarity index 100% rename from builtin/providers/openstack/resource_compute_secgroup.go rename to builtin/providers/openstack/resource_openstack_compute_secgroup.go diff --git a/builtin/providers/openstack/resource_compute_secgrouprule.go b/builtin/providers/openstack/resource_openstack_compute_secgrouprule.go similarity index 100% rename from builtin/providers/openstack/resource_compute_secgrouprule.go rename to builtin/providers/openstack/resource_openstack_compute_secgrouprule.go From 45c4868f902afe7ff2623ff0804d28bce39b3417 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 10 Jan 2015 14:59:59 -0700 Subject: [PATCH 158/295] fix comment --- .../providers/openstack/resource_openstack_compute_instance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance.go index e8aa71848..62d14b636 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance.go @@ -237,7 +237,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error newFlavor, ok := server.Flavor["id"].(string) if !ok { - return fmt.Errorf("Error setting OpenStack server's flavor: %v", newFlavor) + return fmt.Errorf("Error setting OpenStack server's flavor: %v", server.Flavor) } d.Set("flavor_ref", newFlavor) From 476107579034ebc5f1befbbb4044f4f472b16b21 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 10 Jan 2015 15:02:19 -0700 Subject: [PATCH 159/295] crud for neutron networks --- builtin/providers/openstack/config.go | 18 +- builtin/providers/openstack/provider.go | 2 + .../resource_openstack_networking_network.go | 188 ++++++++++++++++++ 3 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 builtin/providers/openstack/resource_openstack_networking_network.go diff --git a/builtin/providers/openstack/config.go b/builtin/providers/openstack/config.go index cb06a9848..d877fcd80 100644 --- a/builtin/providers/openstack/config.go +++ b/builtin/providers/openstack/config.go @@ -10,17 +10,18 @@ type Config struct { Username string Password string IdentityEndpoint string - TenantName string + TenantName string - computeV2Client *gophercloud.ServiceClient + computeV2Client *gophercloud.ServiceClient + networkingV2Client *gophercloud.ServiceClient } func (c *Config) loadAndValidate() error { ao := gophercloud.AuthOptions{ - Username: c.Username, - Password: c.Password, + Username: c.Username, + Password: c.Password, IdentityEndpoint: c.IdentityEndpoint, - TenantName: c.TenantName, + TenantName: c.TenantName, } client, err := openstack.AuthenticatedClient(ao) @@ -31,9 +32,10 @@ func (c *Config) loadAndValidate() error { c.computeV2Client, err = openstack.NewComputeV2(client, gophercloud.EndpointOpts{ Region: c.Region, }) - if err != nil { - return err - } + + c.networkingV2Client, err = openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ + Region: c.Region, + }) return nil } diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index b9bb5d29b..b9f756ef9 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -52,6 +52,8 @@ func Provider() terraform.ResourceProvider { "openstack_compute_keypair": resourceComputeKeypair(), "openstack_compute_secgroup": resourceComputeSecGroup(), "openstack_compute_secgrouprule": resourceComputeSecGroupRule(), + "openstack_networking_network": resourceNetworkingNetwork(), + "openstack_networking_subnet": resourceNetworkingSubnet(), }, ConfigureFunc: configureProvider, diff --git a/builtin/providers/openstack/resource_openstack_networking_network.go b/builtin/providers/openstack/resource_openstack_networking_network.go new file mode 100644 index 000000000..c819aa0f4 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_network.go @@ -0,0 +1,188 @@ +package openstack + +import ( + "fmt" + "log" + "strconv" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud/openstack/networking/v2/networks" +) + +func resourceNetworkingNetwork() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingNetworkCreate, + Read: resourceNetworkingNetworkRead, + Update: resourceNetworkingNetworkUpdate, + Delete: resourceNetworkingNetworkDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "admin_state_up": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "shared": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + }, + } +} + +func resourceNetworkingNetworkCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + createOpts := networks.CreateOpts{ + Name: d.Get("name").(string), + TenantID: d.Get("tenant_id").(string), + } + + asuRaw := d.Get("admin_state_up").(string) + if asuRaw != "" { + asu, err := strconv.ParseBool(asuRaw) + if err != nil { + return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") + } + createOpts.AdminStateUp = &asu + } + + sharedRaw := d.Get("shared").(string) + if sharedRaw != "" { + shared, err := strconv.ParseBool(sharedRaw) + if err != nil { + return fmt.Errorf("shared, if provided, must be either 'true' or 'false': %v", err) + } + createOpts.Shared = &shared + } + + log.Printf("[INFO] Requesting network creation") + n, err := networks.Create(osClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack Neutron network: %s", err) + } + log.Printf("[INFO] Network ID: %s", n.ID) + + d.SetId(n.ID) + + return resourceNetworkingNetworkRead(d, meta) +} + +func resourceNetworkingNetworkRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + n, err := networks.Get(osClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error retrieving OpenStack Neutron Network: %s", err) + } + + log.Printf("[DEBUG] Retreived Network %s: %+v", d.Id(), n) + + if _, exists := d.GetOk("name"); exists { + if d.HasChange("name") { + d.Set("name", n.Name) + } + } else { + d.Set("name", "") + } + + if _, exists := d.GetOk("admin_state_up"); exists { + if d.HasChange("admin_state_up") { + d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp)) + } + } else { + d.Set("admin_state_up", "") + } + + if _, exists := d.GetOk("shared"); exists { + if d.HasChange("shared") { + d.Set("shared", strconv.FormatBool(n.Shared)) + } + } else { + d.Set("shared", "") + } + + if _, exists := d.GetOk("tenant_id"); exists { + if d.HasChange("tenant_id") { + d.Set("tenant_id", n.TenantID) + } + } else { + d.Set("tenant_id", "") + } + + return nil +} + +func resourceNetworkingNetworkUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + var updateOpts networks.UpdateOpts + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("admin_state_up") { + asuRaw := d.Get("admin_state_up").(string) + if asuRaw != "" { + asu, err := strconv.ParseBool(asuRaw) + if err != nil { + return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") + } + updateOpts.AdminStateUp = &asu + } + } + if d.HasChange("shared") { + sharedRaw := d.Get("shared").(string) + if sharedRaw != "" { + shared, err := strconv.ParseBool(sharedRaw) + if err != nil { + return fmt.Errorf("shared, if provided, must be either 'true' or 'false': %v", err) + } + updateOpts.Shared = &shared + } + } + if d.HasChange("tenant_id") { + updateOpts.TenantID = d.Get("tenant_id").(string) + } + + // If there's nothing to update, don't waste an HTTP call. + if updateOpts != (networks.UpdateOpts{}) { + log.Printf("[DEBUG] Updating Network %s with options: %+v", d.Id(), updateOpts) + + _, err := networks.Update(osClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Network: %s", err) + } + } + + return resourceNetworkingNetworkRead(d, meta) +} + +func resourceNetworkingNetworkDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + err := networks.Delete(osClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack Neutron Network: %s", err) + } + + d.SetId("") + return nil +} From 4424828d25520542e3fda8f7fcfdd6d610869ea9 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 10 Jan 2015 15:03:01 -0700 Subject: [PATCH 160/295] crud for neutron subnets --- .../resource_openstack_networking_subnet.go | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_networking_subnet.go diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet.go b/builtin/providers/openstack/resource_openstack_networking_subnet.go new file mode 100644 index 000000000..bedad5572 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_subnet.go @@ -0,0 +1,294 @@ +package openstack + +import ( + "fmt" + "log" + "strconv" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/jrperritt/terraform/helper/hashcode" + "github.com/rackspace/gophercloud/openstack/networking/v2/subnets" +) + +func resourceNetworkingSubnet() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingSubnetCreate, + Read: resourceNetworkingSubnetRead, + Update: resourceNetworkingSubnetUpdate, + Delete: resourceNetworkingSubnetDelete, + + Schema: map[string]*schema.Schema{ + "network_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "cidr": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "allocation_pools": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "end": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + + "gateway_ip": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "ip_version": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "enable_dhcp": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "dns_nameservers": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: false, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + + "host_routes": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: false, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "destination_cidr": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "next_hop": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + } +} + +func resourceNetworkingSubnetCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + createOpts := subnets.CreateOpts{ + NetworkID: d.Get("network_id").(string), + CIDR: d.Get("cidr").(string), + Name: d.Get("name").(string), + TenantID: d.Get("tenant_id").(string), + AllocationPools: resourceSubnetAllocationPools(d), + GatewayIP: d.Get("gateway_ip").(string), + IPVersion: d.Get("ip_version").(int), + DNSNameservers: resourceSubnetDNSNameservers(d), + HostRoutes: resourceSubnetHostRoutes(d), + } + + edRaw := d.Get("enable_dhcp").(string) + if edRaw != "" { + ed, err := strconv.ParseBool(edRaw) + if err != nil { + return fmt.Errorf("enable_dhcp, if provided, must be either 'true' or 'false'") + } + createOpts.EnableDHCP = &ed + } + + log.Printf("[INFO] Requesting subnet creation") + s, err := subnets.Create(osClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack Neutron subnet: %s", err) + } + log.Printf("[INFO] Subnet ID: %s", s.ID) + + d.SetId(s.ID) + + return resourceNetworkingSubnetRead(d, meta) +} + +func resourceNetworkingSubnetRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + s, err := subnets.Get(osClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error retrieving OpenStack Neutron Subnet: %s", err) + } + + log.Printf("[DEBUG] Retreived Subnet %s: %+v", d.Id(), s) + + d.Set("newtork_id", s.NetworkID) + d.Set("cidr", s.CIDR) + d.Set("ip_version", s.IPVersion) + + if _, exists := d.GetOk("name"); exists { + if d.HasChange("name") { + d.Set("name", s.Name) + } + } else { + d.Set("name", "") + } + + if _, exists := d.GetOk("tenant_id"); exists { + if d.HasChange("tenant_id") { + d.Set("tenant_id", s.Name) + } + } else { + d.Set("tenant_id", "") + } + + if _, exists := d.GetOk("allocation_pools"); exists { + d.Set("allocation_pools", s.AllocationPools) + } + + if _, exists := d.GetOk("gateway_ip"); exists { + if d.HasChange("gateway_ip") { + d.Set("gateway_ip", s.Name) + } + } else { + d.Set("gateway_ip", "") + } + + if _, exists := d.GetOk("enable_dhcp"); exists { + if d.HasChange("enable_dhcp") { + d.Set("enable_dhcp", strconv.FormatBool(s.EnableDHCP)) + } + } else { + d.Set("enable_dhcp", "") + } + + if _, exists := d.GetOk("dns_nameservers"); exists { + d.Set("dns_nameservers", s.DNSNameservers) + } + + if _, exists := d.GetOk("host_routes"); exists { + d.Set("host_routes", s.HostRoutes) + } + + return nil +} + +func resourceNetworkingSubnetUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + var updateOpts subnets.UpdateOpts + + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + + if d.HasChange("gateway_ip") { + updateOpts.GatewayIP = d.Get("gateway_ip").(string) + } + + if d.HasChange("dns_nameservers") { + updateOpts.DNSNameservers = resourceSubnetDNSNameservers(d) + } + + if d.HasChange("host_routes") { + updateOpts.HostRoutes = resourceSubnetHostRoutes(d) + } + + if d.HasChange("enable_dhcp") { + edRaw := d.Get("enable_dhcp").(string) + if edRaw != "" { + ed, err := strconv.ParseBool(edRaw) + if err != nil { + return fmt.Errorf("enable_dhcp, if provided, must be either 'true' or 'false'") + } + updateOpts.EnableDHCP = &ed + } + } + + log.Printf("[DEBUG] Updating Subnet %s with options: %+v", d.Id(), updateOpts) + + _, err := subnets.Update(osClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Subnet: %s", err) + } + + return resourceNetworkingSubnetRead(d, meta) +} + +func resourceNetworkingSubnetDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + err := subnets.Delete(osClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack Neutron Subnet: %s", err) + } + + d.SetId("") + return nil +} + +func resourceSubnetAllocationPools(d *schema.ResourceData) []subnets.AllocationPool { + rawAPs := d.Get("allocation_pools").([]interface{}) + aps := make([]subnets.AllocationPool, len(rawAPs)) + for i, raw := range rawAPs { + aps[i] = raw.(subnets.AllocationPool) + } + return aps +} + +func resourceSubnetDNSNameservers(d *schema.ResourceData) []string { + rawDNSN := d.Get("dns_nameservers").(*schema.Set) + dnsn := make([]string, rawDNSN.Len()) + for i, raw := range rawDNSN.List() { + dnsn[i] = raw.(string) + } + return dnsn +} + +func resourceSubnetHostRoutes(d *schema.ResourceData) []subnets.HostRoute { + rawHR := d.Get("host_routes").([]interface{}) + hr := make([]subnets.HostRoute, len(rawHR)) + for i, raw := range rawHR { + hr[i] = raw.(subnets.HostRoute) + } + return hr +} From ac79c76b132ce5d0c00690d9f47c4af6b21627b3 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 14 Jan 2015 11:53:34 -0700 Subject: [PATCH 161/295] add lb resources --- builtin/providers/openstack/provider.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index b9f756ef9..9426fc23a 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -52,6 +52,10 @@ func Provider() terraform.ResourceProvider { "openstack_compute_keypair": resourceComputeKeypair(), "openstack_compute_secgroup": resourceComputeSecGroup(), "openstack_compute_secgrouprule": resourceComputeSecGroupRule(), + "openstack_lb_member": resourceLBMember(), + "openstack_lb_monitor": resourceLBMonitor(), + "openstack_lb_pool": resourceLBPool(), + "openstack_lb_vip": resourceLBVip(), "openstack_networking_network": resourceNetworkingNetwork(), "openstack_networking_subnet": resourceNetworkingSubnet(), }, From 9077b6604528327ad4d4294d286682ccbdb1e00b Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 14 Jan 2015 11:54:27 -0700 Subject: [PATCH 162/295] lb member operations --- .../openstack/resource_openstack_lb_member.go | 131 ++++++++++++++++++ .../openstack/r/lb_member.html.markdown | 52 +++++++ 2 files changed, 183 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_lb_member.go create mode 100644 website/source/docs/providers/openstack/r/lb_member.html.markdown diff --git a/builtin/providers/openstack/resource_openstack_lb_member.go b/builtin/providers/openstack/resource_openstack_lb_member.go new file mode 100644 index 000000000..c628ee2b4 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_member.go @@ -0,0 +1,131 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members" +) + +func resourceLBMember() *schema.Resource { + return &schema.Resource{ + Create: resourceLBMemberCreate, + Read: resourceLBMemberRead, + Update: resourceLBMemberUpdate, + Delete: resourceLBMemberDelete, + + Schema: map[string]*schema.Schema{ + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "pool_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "admin_state_up": &schema.Schema{ + Type: schema.TypeBool, + Required: true, + ForceNew: false, + }, + }, + } +} + +func resourceLBMemberCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + createOpts := members.CreateOpts{ + //TenantID: d.Get("tenant_id").(string), + Address: d.Get("address").(string), + ProtocolPort: d.Get("port").(int), + PoolID: d.Get("pool_id").(string), + } + + log.Printf("[INFO] Requesting lb member creation") + p, err := members.Create(osClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack LB member: %s", err) + } + log.Printf("[INFO] LB Member ID: %s", p.ID) + + d.SetId(p.ID) + + return resourceLBMemberRead(d, meta) +} + +func resourceLBMemberRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + p, err := members.Get(osClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error retrieving OpenStack LB Member: %s", err) + } + + log.Printf("[DEBUG] Retreived OpenStack LB Member %s: %+v", d.Id(), p) + + d.Set("address", p.Address) + d.Set("port", p.ProtocolPort) + d.Set("pool_id", p.PoolID) + + if _, exists := d.GetOk("tenant_id"); exists { + if d.HasChange("tenant_id") { + d.Set("tenant_id", p.TenantID) + } + } else { + d.Set("tenant_id", "") + } + + return nil +} + +func resourceLBMemberUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + var updateOpts members.UpdateOpts + if d.HasChange("admin_state_up") { + updateOpts.AdminStateUp = d.Get("admin_state_up").(bool) + } + + log.Printf("[DEBUG] Updating OpenStack LB Member %s with options: %+v", d.Id(), updateOpts) + + _, err := members.Update(osClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack LB Member: %s", err) + } + + return resourceLBMemberRead(d, meta) +} + +func resourceLBMemberDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + err := members.Delete(osClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack LB Member: %s", err) + } + + d.SetId("") + return nil +} diff --git a/website/source/docs/providers/openstack/r/lb_member.html.markdown b/website/source/docs/providers/openstack/r/lb_member.html.markdown new file mode 100644 index 000000000..0607ca840 --- /dev/null +++ b/website/source/docs/providers/openstack/r/lb_member.html.markdown @@ -0,0 +1,52 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_lb_member" +sidebar_current: "docs-openstack-resource-lb-member" +description: |- + Manages a load balancer member resource within OpenStack. +--- + +# openstack\_lb\_member + +Manages a load balancer member resource within OpenStack. + +## Example Usage + +``` +resource "openstack_lb_member" "node_1" { + address = "196.172.0.1" + port = 80 + pool_id = "$12345" + admin_state_up = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `address` - (Required) The IP address of the member. Changing this creates a + new member. + +* `port` - (Required) An integer representing the port on which the member is + hosted. Changing this creates a new member. + +* `pool_id` - (Required) The pool to which this member will belong. Changing + this creates a new member. + +* `admin_state_up` - (Optional) The administrative state of the member. + Acceptable values are 'true' and 'false'. Changing this value updates the + state of the existing member. + +* `tenant_id` - (Optional) The owner of the member. Required if admin wants to + create a pool member for another tenant. Changing this creates a new member. + +## Attributes Reference + +The following attributes are exported: + +* `address` - See Argument Reference above. +* `port` - See Argument Reference above. +* `pool_id` - See Argument Reference above. +* `admin_state_up` - See Argument Reference above. +* `tenant_id` - See Argument Reference above. From 966c8420d1bd8a5ab23c96d6d148e3a233100d1e Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 14 Jan 2015 11:54:52 -0700 Subject: [PATCH 163/295] lb monitor operations --- .../resource_openstack_lb_monitor.go | 227 ++++++++++++++++++ .../openstack/r/lb_monitor.html.markdown | 76 ++++++ 2 files changed, 303 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_lb_monitor.go create mode 100644 website/source/docs/providers/openstack/r/lb_monitor.html.markdown diff --git a/builtin/providers/openstack/resource_openstack_lb_monitor.go b/builtin/providers/openstack/resource_openstack_lb_monitor.go new file mode 100644 index 000000000..b2cb98641 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_monitor.go @@ -0,0 +1,227 @@ +package openstack + +import ( + "fmt" + "log" + "strconv" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors" +) + +func resourceLBMonitor() *schema.Resource { + return &schema.Resource{ + Create: resourceLBMonitorCreate, + Read: resourceLBMonitorRead, + Update: resourceLBMonitorUpdate, + Delete: resourceLBMonitorDelete, + + Schema: map[string]*schema.Schema{ + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "delay": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: false, + }, + + "timeout": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: false, + }, + + "max_retries": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: false, + }, + + "url_path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "http_method": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "expected_codes": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "admin_state_up": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + }, + } +} + +func resourceLBMonitorCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + createOpts := monitors.CreateOpts{ + TenantID: d.Get("tenant_id").(string), + Type: d.Get("type").(string), + Delay: d.Get("delay").(int), + Timeout: d.Get("timeout").(int), + MaxRetries: d.Get("max_retries").(int), + URLPath: d.Get("url_path").(string), + ExpectedCodes: d.Get("expected_codes").(string), + HTTPMethod: d.Get("http_method").(string), + } + + asuRaw := d.Get("admin_state_up").(string) + if asuRaw != "" { + asu, err := strconv.ParseBool(asuRaw) + if err != nil { + return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") + } + createOpts.AdminStateUp = &asu + } + + log.Printf("[INFO] Requesting lb monitor creation") + m, err := monitors.Create(osClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack LB Monitor: %s", err) + } + log.Printf("[INFO] LB Monitor ID: %s", m.ID) + + d.SetId(m.ID) + + return resourceLBMonitorRead(d, meta) +} + +func resourceLBMonitorRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + m, err := monitors.Get(osClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error retrieving OpenStack LB Monitor: %s", err) + } + + log.Printf("[DEBUG] Retreived OpenStack LB Monitor %s: %+v", d.Id(), m) + + d.Set("type", m.Type) + d.Set("delay", m.Delay) + d.Set("timeout", m.Timeout) + d.Set("max_retries", m.MaxRetries) + + if _, exists := d.GetOk("tenant_id"); exists { + if d.HasChange("tenant_id") { + d.Set("tenant_id", m.TenantID) + } + } else { + d.Set("tenant_id", "") + } + + if _, exists := d.GetOk("url_path"); exists { + if d.HasChange("url_path") { + d.Set("url_path", m.URLPath) + } + } else { + d.Set("url_path", "") + } + + if _, exists := d.GetOk("http_method"); exists { + if d.HasChange("http_method") { + d.Set("http_method", m.HTTPMethod) + } + } else { + d.Set("http_method", "") + } + + if _, exists := d.GetOk("expected_codes"); exists { + if d.HasChange("expected_codes") { + d.Set("expected_codes", m.ExpectedCodes) + } + } else { + d.Set("expected_codes", "") + } + + if _, exists := d.GetOk("admin_state_up"); exists { + if d.HasChange("admin_state_up") { + d.Set("admin_state_up", strconv.FormatBool(m.AdminStateUp)) + } + } else { + d.Set("admin_state_up", "") + } + + return nil +} + +func resourceLBMonitorUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + var updateOpts monitors.UpdateOpts + if d.HasChange("delay") { + updateOpts.Delay = d.Get("delay").(int) + } + if d.HasChange("timeout") { + updateOpts.Timeout = d.Get("timeout").(int) + } + if d.HasChange("max_retries") { + updateOpts.MaxRetries = d.Get("max_retries").(int) + } + if d.HasChange("url_path") { + updateOpts.URLPath = d.Get("url_path").(string) + } + if d.HasChange("http_method") { + updateOpts.HTTPMethod = d.Get("http_method").(string) + } + if d.HasChange("expected_codes") { + updateOpts.ExpectedCodes = d.Get("expected_codes").(string) + } + if d.HasChange("admin_state_up") { + asuRaw := d.Get("admin_state_up").(string) + if asuRaw != "" { + asu, err := strconv.ParseBool(asuRaw) + if err != nil { + return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") + } + updateOpts.AdminStateUp = &asu + } + } + + log.Printf("[DEBUG] Updating OpenStack LB Monitor %s with options: %+v", d.Id(), updateOpts) + + _, err := monitors.Update(osClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack LB Monitor: %s", err) + } + + return resourceLBMonitorRead(d, meta) +} + +func resourceLBMonitorDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + err := monitors.Delete(osClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack LB Monitor: %s", err) + } + + d.SetId("") + return nil +} diff --git a/website/source/docs/providers/openstack/r/lb_monitor.html.markdown b/website/source/docs/providers/openstack/r/lb_monitor.html.markdown new file mode 100644 index 000000000..36b814274 --- /dev/null +++ b/website/source/docs/providers/openstack/r/lb_monitor.html.markdown @@ -0,0 +1,76 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_lb_monitor" +sidebar_current: "docs-openstack-resource-lb-monitor" +description: |- + Manages a load balancer monitor resource within OpenStack. +--- + +# openstack\_lb\_monitor + +Manages a load balancer monitor resource within OpenStack. + +## Example Usage + +``` +resource "openstack_lb_monitor" "monitor_1" { + type = "PING" + delay = 30 + timeout = 5 + max_retries = 3 + admin_state_up = "true" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `type` - (Required) The type of probe, which is PING, TCP, HTTP, or HTTPS, + that is sent by the monitor to verify the member state. Changing this + creates a new monitor. + +* `delay` - (Required) The time, in seconds, between sending probes to members. + Changing this creates a new monitor. + +* `timeout` - (Required) Maximum number of seconds for a monitor to wait for a + ping reply before it times out. The value must be less than the delay value. + Changing this updates the timeout of the existing monitor. + +* `max_retries` - (Required) Number of permissible ping failures before changing + the member's status to INACTIVE. Must be a number between 1 and 10. Changing + this updates the max_retries of the existing monitor. + +* `url_path` - (Optional) Required for HTTP(S) types. URI path that will be + accessed if monitor type is HTTP or HTTPS. Changing this updates the + url_path of the existing monitor. + +* `http_method` - (Optional) Required for HTTP(S) types. The HTTP method used + for requests by the monitor. If this attribute is not specified, it defaults + to "GET". Changing this updates the http_method of the existing monitor. + +* `expected_codes` - (Optional) equired for HTTP(S) types. Expected HTTP codes + for a passing HTTP(S) monitor. You can either specify a single status like + "200", or a range like "200-202". Changing this updates the expected_codes + of the existing monitor. + +* `admin_state_up` - (Optional) The administrative state of the monitor. + Acceptable values are "true" and "false". Changing this value updates the + state of the existing monitor. + +* `tenant_id` - (Optional) The owner of the monitor. Required if admin wants to + create a monitor for another tenant. Changing this creates a new monitor. + +## Attributes Reference + +The following attributes are exported: + +* `type` - See Argument Reference above. +* `delay` - See Argument Reference above. +* `timeout` - See Argument Reference above. +* `max_retries` - See Argument Reference above. +* `url_path` - See Argument Reference above. +* `http_method` - See Argument Reference above. +* `expected_codes` - See Argument Reference above. +* `admin_state_up` - See Argument Reference above. +* `tenant_id` - See Argument Reference above. From 7132b02a84035ea1d16ad246bb7d78b439ebb392 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 14 Jan 2015 11:55:17 -0700 Subject: [PATCH 164/295] lb pool operations --- .../openstack/resource_openstack_lb_pool.go | 194 ++++++++++++++++++ .../openstack/r/lb_pool.html.markdown | 58 ++++++ 2 files changed, 252 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_lb_pool.go create mode 100644 website/source/docs/providers/openstack/r/lb_pool.html.markdown diff --git a/builtin/providers/openstack/resource_openstack_lb_pool.go b/builtin/providers/openstack/resource_openstack_lb_pool.go new file mode 100644 index 000000000..0fb42463e --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_pool.go @@ -0,0 +1,194 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/jrperritt/terraform/helper/hashcode" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools" +) + +func resourceLBPool() *schema.Resource { + return &schema.Resource{ + Create: resourceLBPoolCreate, + Read: resourceLBPoolRead, + Update: resourceLBPoolUpdate, + Delete: resourceLBPoolDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "subnet_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "lb_method": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "monitor_ids": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: false, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + }, + } +} + +func resourceLBPoolCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + createOpts := pools.CreateOpts{ + Name: d.Get("name").(string), + Protocol: d.Get("protocol").(string), + SubnetID: d.Get("subnet_id").(string), + LBMethod: d.Get("lb_method").(string), + TenantID: d.Get("tenant_id").(string), + } + + log.Printf("[INFO] Requesting lb pool creation") + p, err := pools.Create(osClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack LB pool: %s", err) + } + log.Printf("[INFO] LB Pool ID: %s", p.ID) + + d.SetId(p.ID) + + if mIDs := resourcePoolMonitorIDs(d); mIDs != nil { + for _, mID := range mIDs { + _, err := pools.AssociateMonitor(osClient, p.ID, mID).Extract() + if err != nil { + return fmt.Errorf("Error associating monitor (%s) with OpenStack LB pool (%s): %s", mID, p.ID, err) + } + } + } + + return resourceLBPoolRead(d, meta) +} + +func resourceLBPoolRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + p, err := pools.Get(osClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error retrieving OpenStack LB Pool: %s", err) + } + + log.Printf("[DEBUG] Retreived OpenStack LB Pool %s: %+v", d.Id(), p) + + d.Set("name", p.Name) + d.Set("protocol", p.Protocol) + d.Set("subnet_id", p.SubnetID) + d.Set("lb_method", p.LBMethod) + + if _, exists := d.GetOk("tenant_id"); exists { + if d.HasChange("tenant_id") { + d.Set("tenant_id", p.TenantID) + } + } else { + d.Set("tenant_id", "") + } + + d.Set("monitor_ids", p.MonitorIDs) + + return nil +} + +func resourceLBPoolUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + var updateOpts pools.UpdateOpts + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("lb_method") { + updateOpts.LBMethod = d.Get("lb_method").(string) + } + + log.Printf("[DEBUG] Updating OpenStack LB Pool %s with options: %+v", d.Id(), updateOpts) + + _, err := pools.Update(osClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack LB Pool: %s", err) + } + + if d.HasChange("monitor_ids") { + oldMIDsRaw, newMIDsRaw := d.GetChange("security_groups") + oldMIDsSet, newMIDsSet := oldMIDsRaw.(*schema.Set), newMIDsRaw.(*schema.Set) + monitorsToAdd := newMIDsSet.Difference(oldMIDsSet) + monitorsToRemove := oldMIDsSet.Difference(newMIDsSet) + + log.Printf("[DEBUG] Monitors to add: %v", monitorsToAdd) + + log.Printf("[DEBUG] Monitors to remove: %v", monitorsToRemove) + + for _, m := range monitorsToAdd.List() { + _, err := pools.AssociateMonitor(osClient, d.Id(), m.(string)).Extract() + if err != nil { + return fmt.Errorf("Error associating monitor (%s) with OpenStack server (%s): %s", m.(string), d.Id(), err) + } + log.Printf("[DEBUG] Associated monitor (%s) with pool (%s)", m.(string), d.Id()) + } + + for _, m := range monitorsToRemove.List() { + _, err := pools.DisassociateMonitor(osClient, d.Id(), m.(string)).Extract() + if err != nil { + return fmt.Errorf("Error disassociating monitor (%s) from OpenStack server (%s): %s", m.(string), d.Id(), err) + } + log.Printf("[DEBUG] Disassociated monitor (%s) from pool (%s)", m.(string), d.Id()) + } + } + + return resourceLBPoolRead(d, meta) +} + +func resourceLBPoolDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + err := pools.Delete(osClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack LB Pool: %s", err) + } + + d.SetId("") + return nil +} + +func resourcePoolMonitorIDs(d *schema.ResourceData) []string { + mIDsRaw := d.Get("monitor_ids").(*schema.Set) + mIDs := make([]string, mIDsRaw.Len()) + for i, raw := range mIDsRaw.List() { + mIDs[i] = raw.(string) + } + return mIDs +} diff --git a/website/source/docs/providers/openstack/r/lb_pool.html.markdown b/website/source/docs/providers/openstack/r/lb_pool.html.markdown new file mode 100644 index 000000000..90e3ed0c9 --- /dev/null +++ b/website/source/docs/providers/openstack/r/lb_pool.html.markdown @@ -0,0 +1,58 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_lb_pool" +sidebar_current: "docs-openstack-resource-lb-pool" +description: |- + Manages a load balancer pool resource within OpenStack. +--- + +# openstack\_lb\_pool + +Manages a load balancer pool resource within OpenStack. + +## Example Usage + +``` +resource "openstack_lb_pool" "pool_1" { + name = "tf_test_lb_pool" + protocol = "HTTP" + subnet_id = "12345" + lb_method = "ROUND_ROBIN" + monitor_id = "67890" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the pool. Changing this updates the name of + the existing pool. + +* `protocol` - (Required) The protocol used by the pool members, you can use + either 'TCP, 'HTTP', or 'HTTPS'. Changing this creates a new pool. + +* `subnet_id` - (Required) The network on which the members of the pool will be + located. Only members that are on this network can be added to the pool. + Changing this creates a new pool. + +* `lb_method` - (Required) The algorithm used to distribute load between the + members of the pool. The current specification supports 'ROUND_ROBIN' and + 'LEAST_CONNECTIONS' as valid values for this attribute. + +* `tenant_id` - (Optional) The owner of the pool. Required if admin wants to + create a pool member for another tenant. Changing this creates a new pool. + +* `monitor_ids` - (Optional) A list of IDs of monitors to associate with the + pool. + +## Attributes Reference + +The following attributes are exported: + +* `name` - See Argument Reference above. +* `protocol` - See Argument Reference above. +* `subnet_id` - See Argument Reference above. +* `lb_method` - See Argument Reference above. +* `tenant_id` - See Argument Reference above. +* `monitor_id` - See Argument Reference above. From c2230a8aaa1b67cbdcbb85b154b73cc53ff3528f Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 14 Jan 2015 11:55:36 -0700 Subject: [PATCH 165/295] lb vip operations --- .../openstack/resource_openstack_lb_vip.go | 256 ++++++++++++++++++ .../openstack/r/lb_vip.html.markdown | 89 ++++++ 2 files changed, 345 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_lb_vip.go create mode 100644 website/source/docs/providers/openstack/r/lb_vip.html.markdown diff --git a/builtin/providers/openstack/resource_openstack_lb_vip.go b/builtin/providers/openstack/resource_openstack_lb_vip.go new file mode 100644 index 000000000..405d01a19 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_vip.go @@ -0,0 +1,256 @@ +package openstack + +import ( + "fmt" + "log" + "strconv" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips" +) + +func resourceLBVip() *schema.Resource { + return &schema.Resource{ + Create: resourceLBVipCreate, + Read: resourceLBVipRead, + Update: resourceLBVipUpdate, + Delete: resourceLBVipDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "subnet_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "pool_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "persistence": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + + "conn_limit": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + ForceNew: false, + }, + + "admin_state_up": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + }, + } +} + +func resourceLBVipCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + createOpts := vips.CreateOpts{ + Name: d.Get("name").(string), + SubnetID: d.Get("subnet_id").(string), + Protocol: d.Get("protocol").(string), + ProtocolPort: d.Get("port").(int), + PoolID: d.Get("pool_id").(string), + TenantID: d.Get("tenant_id").(string), + Address: d.Get("address").(string), + Description: d.Get("description").(string), + Persistence: resourceVipPersistence(d), + ConnLimit: gophercloud.MaybeInt(d.Get("conn_limit").(int)), + } + + asuRaw := d.Get("admin_state_up").(string) + if asuRaw != "" { + asu, err := strconv.ParseBool(asuRaw) + if err != nil { + return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") + } + createOpts.AdminStateUp = &asu + } + + log.Printf("[INFO] Requesting lb vip creation") + p, err := vips.Create(osClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack LB VIP: %s", err) + } + log.Printf("[INFO] LB VIP ID: %s", p.ID) + + d.SetId(p.ID) + + return resourceLBVipRead(d, meta) +} + +func resourceLBVipRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + p, err := vips.Get(osClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error retrieving OpenStack LB VIP: %s", err) + } + + log.Printf("[DEBUG] Retreived OpenStack LB VIP %s: %+v", d.Id(), p) + + d.Set("name", p.Name) + d.Set("subnet_id", p.SubnetID) + d.Set("protocol", p.Protocol) + d.Set("port", p.ProtocolPort) + d.Set("pool_id", p.PoolID) + + if _, exists := d.GetOk("tenant_id"); exists { + if d.HasChange("tenant_id") { + d.Set("tenant_id", p.TenantID) + } + } else { + d.Set("tenant_id", "") + } + + if _, exists := d.GetOk("address"); exists { + if d.HasChange("address") { + d.Set("address", p.Address) + } + } else { + d.Set("address", "") + } + + if _, exists := d.GetOk("description"); exists { + if d.HasChange("description") { + d.Set("description", p.Description) + } + } else { + d.Set("description", "") + } + + if _, exists := d.GetOk("persistence"); exists { + if d.HasChange("persistence") { + d.Set("persistence", p.Description) + } + } + + if _, exists := d.GetOk("conn_limit"); exists { + if d.HasChange("conn_limit") { + d.Set("conn_limit", p.ConnLimit) + } + } else { + d.Set("conn_limit", "") + } + + if _, exists := d.GetOk("admin_state_up"); exists { + if d.HasChange("admin_state_up") { + d.Set("admin_state_up", strconv.FormatBool(p.AdminStateUp)) + } + } else { + d.Set("admin_state_up", "") + } + + return nil +} + +func resourceLBVipUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + var updateOpts vips.UpdateOpts + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("pool_id") { + updateOpts.PoolID = d.Get("pool_id").(string) + } + if d.HasChange("description") { + updateOpts.Description = d.Get("description").(string) + } + if d.HasChange("persistence") { + updateOpts.Persistence = resourceVipPersistence(d) + } + if d.HasChange("conn_limit") { + updateOpts.ConnLimit = gophercloud.MaybeInt(d.Get("conn_limit").(int)) + } + if d.HasChange("admin_state_up") { + asuRaw := d.Get("admin_state_up").(string) + if asuRaw != "" { + asu, err := strconv.ParseBool(asuRaw) + if err != nil { + return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") + } + updateOpts.AdminStateUp = &asu + } + } + + log.Printf("[DEBUG] Updating OpenStack LB VIP %s with options: %+v", d.Id(), updateOpts) + + _, err := vips.Update(osClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack LB VIP: %s", err) + } + + return resourceLBVipRead(d, meta) +} + +func resourceLBVipDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + osClient := config.networkingV2Client + + err := vips.Delete(osClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack LB VIP: %s", err) + } + + d.SetId("") + return nil +} + +func resourceVipPersistence(d *schema.ResourceData) *vips.SessionPersistence { + rawP := d.Get("persistence").(interface{}) + rawMap := rawP.(map[string]interface{}) + p := vips.SessionPersistence{ + Type: rawMap["type"].(string), + CookieName: rawMap["cookie_name"].(string), + } + return &p +} diff --git a/website/source/docs/providers/openstack/r/lb_vip.html.markdown b/website/source/docs/providers/openstack/r/lb_vip.html.markdown new file mode 100644 index 000000000..0eddcaa5f --- /dev/null +++ b/website/source/docs/providers/openstack/r/lb_vip.html.markdown @@ -0,0 +1,89 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_lb_vip" +sidebar_current: "docs-openstack-resource-lb-vip" +description: |- + Manages a load balancer vip resource within OpenStack. +--- + +# openstack\_lb\_vip + +Manages a load balancer vip resource within OpenStack. + +## Example Usage + +``` +resource "openstack_lb_vip" "vip_1" { + name = "tf_test_lb_vip" + subnet_id = "12345" + protocol = "HTTP" + port = 80 + pool_id = "67890" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the vip. Changing this updates the name of + the existing vip. + +* `subnet_id` - (Required) The network on which to allocate the vip's address. A + tenant can only create vips on networks authorized by policy (e.g. networks + that belong to them or networks that are shared). Changing this creates a + new vip. + +* `protocol` - (Required) The protocol - can be either 'TCP, 'HTTP', or + HTTPS'. Changing this creates a new vip. + +* `port` - (Required) The port on which to listen for client traffic. Changing + this creates a new vip. + +* `pool_id` - (Required) The ID of the pool with which the vip is associated. + Changing this updates the pool_id of the existing vip. + +* `tenant_id` - (Optional) The owner of the vip. Required if admin wants to + create a vip member for another tenant. Changing this creates a new vip. + +* `address` - (Optional) The IP address of the vip. Changing this creates a new + vip. + +* `description` - (Optional) Human-readable description for the vip. Changing + this updates the description of the existing vip. + +* `persistence` - (Optional) Omit this field to prevent session persistence. + The persistence object structure is documented below. Changing this updates + the persistence of the existing vip. + +* `conn_limit` - (Optional) The maximum number of connections allowed for the + vip. Default is -1, meaning no limit. Changing this updates the conn_limit + of the existing vip. + +* `admin_state_up` - (Optional) The administrative state of the vip. + Acceptable values are "true" and "false". Changing this value updates the + state of the existing vip. + +The `persistence` block supports: + +* `type` - (Required) The type of persistence mode. Valid values are "SOURCE_IP", + "HTTP_COOKIE", or "APP_COOKIE". + +* `cookie_name` - (Optional) The name of the cookie if persistence mode is set + appropriately. + +## Attributes Reference + +The following attributes are exported: + +* `name` - See Argument Reference above. +* `subnet_id` - See Argument Reference above. +* `protocol` - See Argument Reference above. +* `port` - See Argument Reference above. +* `pool_id` - See Argument Reference above. +* `tenant_id` - See Argument Reference above. +* `address` - See Argument Reference above. +* `description` - See Argument Reference above. +* `persistence` - See Argument Reference above. +* `conn_limit` - See Argument Reference above. +* `admin_state_up` - See Argument Reference above. From ef3ee11045e09925f40009acc72be023c1e7fdbe Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 14 Jan 2015 11:56:52 -0700 Subject: [PATCH 166/295] neutron network operations --- .../resource_openstack_networking_network.go | 16 ++----- .../r/networking_network.html.markdown | 47 +++++++++++++++++++ 2 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 website/source/docs/providers/openstack/r/networking_network.html.markdown diff --git a/builtin/providers/openstack/resource_openstack_networking_network.go b/builtin/providers/openstack/resource_openstack_networking_network.go index c819aa0f4..8f262e369 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network.go +++ b/builtin/providers/openstack/resource_openstack_networking_network.go @@ -38,7 +38,7 @@ func resourceNetworkingNetwork() *schema.Resource { "tenant_id": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: false, + ForceNew: true, }, }, } @@ -157,18 +157,12 @@ func resourceNetworkingNetworkUpdate(d *schema.ResourceData, meta interface{}) e updateOpts.Shared = &shared } } - if d.HasChange("tenant_id") { - updateOpts.TenantID = d.Get("tenant_id").(string) - } - // If there's nothing to update, don't waste an HTTP call. - if updateOpts != (networks.UpdateOpts{}) { - log.Printf("[DEBUG] Updating Network %s with options: %+v", d.Id(), updateOpts) + log.Printf("[DEBUG] Updating Network %s with options: %+v", d.Id(), updateOpts) - _, err := networks.Update(osClient, d.Id(), updateOpts).Extract() - if err != nil { - return fmt.Errorf("Error updating OpenStack Neutron Network: %s", err) - } + _, err := networks.Update(osClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Network: %s", err) } return resourceNetworkingNetworkRead(d, meta) diff --git a/website/source/docs/providers/openstack/r/networking_network.html.markdown b/website/source/docs/providers/openstack/r/networking_network.html.markdown new file mode 100644 index 000000000..eb765cac0 --- /dev/null +++ b/website/source/docs/providers/openstack/r/networking_network.html.markdown @@ -0,0 +1,47 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_networking_network" +sidebar_current: "docs-openstack-resource-networking-network" +description: |- + Manages a Neutron network resource within OpenStack. +--- + +# openstack\_networking\_network + +Manages a Neutron network resource within OpenStack. + +## Example Usage + +``` +resource "openstack_networking_network" "network_1" { + name = "tf_test_network" + admin_state_up = "true" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Optional) The name of the network. Changing this updates the name of + the existing network. + +* `shared` - (Optional) Specifies whether the network resource can be accessed + by any tenant or not. Changing this updates the sharing capabalities of the + existing network. + +* `tenant_id` - (Optional) The owner of the newtork. Required if admin wants to + create a network for another tenant. Changing this creates a new network. + +* `admin_state_up` - (Optional) The administrative state of the network. + Acceptable values are "true" and "false". Changing this value updates the + state of the existing network. + +## Attributes Reference + +The following attributes are exported: + +* `name` - See Argument Reference above. +* `shared` - See Argument Reference above. +* `tenant_id` - See Argument Reference above. +* `admin_state_up` - See Argument Reference above. From 8e6e7909cbb5658978d9adf962d1b8927df0a4eb Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 14 Jan 2015 11:57:47 -0700 Subject: [PATCH 167/295] neutron subnet operations --- .../resource_openstack_networking_subnet.go | 12 ++- .../r/networking_subnet.html.markdown | 92 +++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 website/source/docs/providers/openstack/r/networking_subnet.html.markdown diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet.go b/builtin/providers/openstack/resource_openstack_networking_subnet.go index bedad5572..0f52c0be2 100644 --- a/builtin/providers/openstack/resource_openstack_networking_subnet.go +++ b/builtin/providers/openstack/resource_openstack_networking_subnet.go @@ -270,7 +270,11 @@ func resourceSubnetAllocationPools(d *schema.ResourceData) []subnets.AllocationP rawAPs := d.Get("allocation_pools").([]interface{}) aps := make([]subnets.AllocationPool, len(rawAPs)) for i, raw := range rawAPs { - aps[i] = raw.(subnets.AllocationPool) + rawMap := raw.(map[string]interface{}) + aps[i] = subnets.AllocationPool{ + Start: rawMap["start"].(string), + End: rawMap["end"].(string), + } } return aps } @@ -288,7 +292,11 @@ func resourceSubnetHostRoutes(d *schema.ResourceData) []subnets.HostRoute { rawHR := d.Get("host_routes").([]interface{}) hr := make([]subnets.HostRoute, len(rawHR)) for i, raw := range rawHR { - hr[i] = raw.(subnets.HostRoute) + rawMap := raw.(map[string]interface{}) + hr[i] = subnets.HostRoute{ + DestinationCIDR: rawMap["destination_cidr"].(string), + NextHop: rawMap["next_hop"].(string), + } } return hr } diff --git a/website/source/docs/providers/openstack/r/networking_subnet.html.markdown b/website/source/docs/providers/openstack/r/networking_subnet.html.markdown new file mode 100644 index 000000000..f9ab4f6ed --- /dev/null +++ b/website/source/docs/providers/openstack/r/networking_subnet.html.markdown @@ -0,0 +1,92 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_networking_subnet" +sidebar_current: "docs-openstack-resource-networking-subnet" +description: |- + Manages a Neutron subnet resource within OpenStack. +--- + +# openstack\_networking\_subnet + +Manages a Neutron subnet resource within OpenStack. + +## Example Usage + +``` +resource "openstack_networking_network" "network_1" { + name = "tf_test_network" + admin_state_up = "true" +} + +resource "openstack_networking_subnet" "subnet_1" { + network_id = "${openstack_networking_network.network_1.id}" + cidr = "192.168.199.0/24" + ip_version = 4 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `network_id` - (Required) The UUID of the parent network. Changing this + creates a new subnet. + +* `cidr` - (Required) CIDR representing IP range for this subnet, based on IP + version. Changing this creates a new subnet. + +* `ip_version` - (Required) IP version, either 4 or 6. Changing this creates a + new subnet. + +* `name` - (Optional) The name of the subnet. Changing this updates the name of + the existing subnet. + +* `tenant_id` - (Optional) The owner of the subnet. Required if admin wants to + create a subnet for another tenant. Changing this creates a new subnet. + +* `allocation_pools` - (Optional) An array of sub-ranges of CIDR available for + dynamic allocation to ports. The allocation_pool object structure is + documented below. Changing this creates a new subnet. + +* `gateway_ip` - (Optional) Default gateway used by devices in this subnet. + Changing this updates the gateway IP of the existing subnet. + +* `enable_dhcp` - (Optional) The administrative state of the network. + Acceptable values are "true" and "false". Changing this value enables or + disables the DHCP capabilities of the existing subnet. + +* `dns_nameservers` - (Optional) An array of DNS name server names used by hosts + in this subnet. Changing this updates the DNS name servers for the existing + subnet. + +* `host_routes` - (Optional) An array of routes that should be used by devices + with IPs from this subnet (not including local subnet route). The host_route + object structure is documented below. Changing this updates the host routes + for the existing subnet. + +The `allocation_pools` block supports: + +* `start` - (Required) The starting address. + +* `end` - (Required) The ending address. + +The `host_routes` block supports: + +* `destination_cidr` - (Required) The destination CIDR. + +* `next_hop` - (Required) The next hop in the route. + +## Attributes Reference + +The following attributes are exported: + +* `network_id` - See Argument Reference above. +* `cidr` - See Argument Reference above. +* `ip_version` - See Argument Reference above. +* `name` - See Argument Reference above. +* `tenant_id` - See Argument Reference above. +* `allocation_pools` - See Argument Reference above. +* `gateway_ip` - See Argument Reference above. +* `enable_dhcp` - See Argument Reference above. +* `dns_nameservers` - See Argument Reference above. +* `host_routes` - See Argument Reference above. From dd4155fa80093b52a38cd678c967d383e4678fcd Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 14 Jan 2015 11:58:52 -0700 Subject: [PATCH 168/295] set host in read function --- .../providers/openstack/resource_openstack_compute_instance.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance.go index 62d14b636..e101e45f7 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance.go @@ -205,10 +205,12 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error pa := paRaw.(map[string]interface{}) if pa["version"].(float64) == 4 { host = pa["addr"].(string) + d.Set("access_ip_v4", host) } } } } + d.Set("host", host) log.Printf("host: %s", host) From 2a4570e382ea7e38e75f54e54684657dea5c33e3 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 14 Jan 2015 12:00:39 -0700 Subject: [PATCH 169/295] add comment for changing 'networks' attribute --- .../docs/providers/openstack/r/compute_instance.html.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/source/docs/providers/openstack/r/compute_instance.html.markdown b/website/source/docs/providers/openstack/r/compute_instance.html.markdown index f22624bf9..7de1cd1b9 100644 --- a/website/source/docs/providers/openstack/r/compute_instance.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance.html.markdown @@ -45,7 +45,8 @@ The following arguments are supported: the server. Changing this creates a new server. * `networks` - (Optional) An array of one or more networks to attach to the - instance. The network object structure is documented below. + instance. The network object structure is documented below. Changing this + creates a new server. * `metadata` - (Optional) Metadata key/value pairs to make available from within the instance. Changing this updates the existing server metadata. From bf92b1647fa1ce86e142a45a3fe972548e25bb11 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 14 Jan 2015 12:01:20 -0700 Subject: [PATCH 170/295] fix typo --- .../providers/openstack/r/compute_secgrouprule.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/providers/openstack/r/compute_secgrouprule.html.markdown b/website/source/docs/providers/openstack/r/compute_secgrouprule.html.markdown index 95b92981c..60ffb347c 100644 --- a/website/source/docs/providers/openstack/r/compute_secgrouprule.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_secgrouprule.html.markdown @@ -43,11 +43,11 @@ The following arguments are supported: * `ip_protocol` - (Required) The protocol type that will be allowed. Changing this creates a new security group rule. -* `cidr` - (Optional) Required is `from_group_id` is empty. The IP range that +* `cidr` - (Optional) Required if `from_group_id` is empty. The IP range that will be the source of network traffic to the security group. Use 0.0.0.0./0 to allow all IP addresses. Changing this creates a new security group rule. -* `from_group_id - (Optional) Required is `cidr` is empty. The ID of a group +* `from_group_id - (Optional) Required if `cidr` is empty. The ID of a group from which to forward traffic to the parent group. Changing this creates a new security group rule. From f4a22ae47ff7d75c392e81b237cb963df96f9fc9 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 14 Jan 2015 12:01:52 -0700 Subject: [PATCH 171/295] add neutron and lb resources --- website/source/layouts/openstack.erb | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/website/source/layouts/openstack.erb b/website/source/layouts/openstack.erb index 69bc4ba2e..b7878887c 100644 --- a/website/source/layouts/openstack.erb +++ b/website/source/layouts/openstack.erb @@ -20,10 +20,28 @@ openstack_compute_keypair > - openstack_compute_keypair + openstack_compute_secgroup > - openstack_compute_keypair + openstack_compute_secgrouprule + + > + openstack_lb_member + + > + openstack_lb_monitor + + > + openstack_lb_pool + + > + openstack_lb_vip + + > + openstack_networking_network + + > + openstack_networking_subnet From cc1445d760495ee7ae0cbdd18a8a2c9d4e634048 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 18 Jan 2015 20:11:20 -0700 Subject: [PATCH 172/295] if update func called, there's something to update --- .../openstack/resource_openstack_compute_instance.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance.go index e101e45f7..5ef061c54 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance.go @@ -261,14 +261,11 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err updateOpts.AccessIPv4 = d.Get("access_ip_v6").(string) } - // If there's nothing to update, don't waste an HTTP call. - if updateOpts != (servers.UpdateOpts{}) { - log.Printf("[DEBUG] Updating Server %s with options: %+v", d.Id(), updateOpts) + log.Printf("[DEBUG] Updating Server %s with options: %+v", d.Id(), updateOpts) - _, err := servers.Update(osClient, d.Id(), updateOpts).Extract() - if err != nil { - return fmt.Errorf("Error updating OpenStack server: %s", err) - } + _, err := servers.Update(osClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack server: %s", err) } if d.HasChange("metadata") { From b1b693e46136da3a8dfc5fa00b4b15e1b1670ff7 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 25 Jan 2015 20:13:39 -0700 Subject: [PATCH 173/295] region is resource-specific; doesn't belong with auth --- builtin/providers/openstack/provider.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 9426fc23a..764266cd4 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -11,13 +11,6 @@ import ( func Provider() terraform.ResourceProvider { return &schema.Provider{ Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ - Type: schema.TypeString, - Required: true, - DefaultFunc: envDefaultFunc("OS_REGION_NAME"), - Description: descriptions["region"], - }, - "auth_url": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -36,7 +29,6 @@ func Provider() terraform.ResourceProvider { Type: schema.TypeString, Optional: true, DefaultFunc: envDefaultFunc("OS_TENANT_NAME"), - //Description: descriptions["tenantname"], }, "password": &schema.Schema{ From b0e8cd5dd3ac7f18d87cd9c0a6452a3be0c22da2 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 25 Jan 2015 20:59:01 -0700 Subject: [PATCH 174/295] a more general way of generating provider clients --- builtin/providers/openstack/clients.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 builtin/providers/openstack/clients.go diff --git a/builtin/providers/openstack/clients.go b/builtin/providers/openstack/clients.go new file mode 100644 index 000000000..b08804467 --- /dev/null +++ b/builtin/providers/openstack/clients.go @@ -0,0 +1,25 @@ +package openstack + +import ( + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" +) + +func newClient(c *Config, service, region string, version int) (*gophercloud.ServiceClient, error) { + var serviceClient *gophercloud.ServiceClient + switch service { + case "compute": + if version == 2 { + serviceClient, err = openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{ + Region: region, + }) + } + case "networking": + if version == 2 { + serviceClient, err = openstack.NewNetworkV2(c.osClient, gophercloud.EndpointOpts{ + Region: region, + }) + } + } + return serviceClient, err +} From bfe492d4077ba2cedca46d9fc4b858d1afa7ed83 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 25 Jan 2015 21:00:57 -0700 Subject: [PATCH 175/295] add options for openstack identity v3 --- builtin/providers/openstack/config.go | 22 +++++----- builtin/providers/openstack/provider.go | 56 +++++++++++++++---------- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/builtin/providers/openstack/config.go b/builtin/providers/openstack/config.go index d877fcd80..215817bdd 100644 --- a/builtin/providers/openstack/config.go +++ b/builtin/providers/openstack/config.go @@ -6,22 +6,30 @@ import ( ) type Config struct { - Region string Username string + UserID string Password string + APIKey string IdentityEndpoint string + TenantID string TenantName string + DomainID string + DomainName string - computeV2Client *gophercloud.ServiceClient - networkingV2Client *gophercloud.ServiceClient + osClient *gophercloud.ProviderClient } func (c *Config) loadAndValidate() error { ao := gophercloud.AuthOptions{ Username: c.Username, + UserID: c.UserID, Password: c.Password, + APIKey: c.APIKey, IdentityEndpoint: c.IdentityEndpoint, + TenantID: c.TenantID, TenantName: c.TenantName, + DomainID: c.DomainID, + DomainName: c.DomainName, } client, err := openstack.AuthenticatedClient(ao) @@ -29,13 +37,7 @@ func (c *Config) loadAndValidate() error { return err } - c.computeV2Client, err = openstack.NewComputeV2(client, gophercloud.EndpointOpts{ - Region: c.Region, - }) - - c.networkingV2Client, err = openstack.NewNetworkV2(client, gophercloud.EndpointOpts{ - Region: c.Region, - }) + c.osClient = client return nil } diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 764266cd4..8b3e37a58 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -15,27 +15,46 @@ func Provider() terraform.ResourceProvider { Type: schema.TypeString, Required: true, DefaultFunc: envDefaultFunc("OS_AUTH_URL"), - Description: descriptions["auth_url"], }, - - "username": &schema.Schema{ + "user_name": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, DefaultFunc: envDefaultFunc("OS_USERNAME"), - Description: descriptions["username"], }, - + "user_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: envDefaultFunc("OS_USERID"), + }, + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: envDefaultFunc("OS_TENANT_ID"), + }, "tenant_name": &schema.Schema{ Type: schema.TypeString, Optional: true, DefaultFunc: envDefaultFunc("OS_TENANT_NAME"), }, - "password": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, DefaultFunc: envDefaultFunc("OS_PASSWORD"), - Description: descriptions["password"], + }, + "api_key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: envDefaultFunc("OS_API_KEY"), + }, + "domain_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: envDefaultFunc("OS_DOMAIN_ID"), + }, + "domain_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: envDefaultFunc("OS_DOMAIN_NAME"), }, }, @@ -58,11 +77,15 @@ func Provider() terraform.ResourceProvider { func configureProvider(d *schema.ResourceData) (interface{}, error) { config := Config{ - Region: d.Get("region").(string), IdentityEndpoint: d.Get("auth_url").(string), - Username: d.Get("username").(string), + Username: d.Get("user_name").(string), + UserID: d.Get("user_id").(string), Password: d.Get("password").(string), + APIKey: d.Get("api_key").(string), + TenantID: d.Get("tenant_id").(string), TenantName: d.Get("tenant_name").(string), + DomainID: d.Get("domain_id").(string), + DomainName: d.Get("domain_name").(string), } if err := config.loadAndValidate(); err != nil { @@ -81,14 +104,3 @@ func envDefaultFunc(k string) schema.SchemaDefaultFunc { return nil, nil } } - -var descriptions map[string]string - -func init() { - descriptions = map[string]string{ - "region": "The region where OpenStack operations will take place.", - "auth_url": "The endpoint against which to authenticate.", - "username": "The username with which to authenticate.", - "password": "The password with which to authenticate.", - } -} From 8579c8693adf887ee97695383ff7a321d0478ac1 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 10:54:07 -0700 Subject: [PATCH 176/295] make 'region' resource-specific; create new client for each crud operation --- builtin/providers/openstack/clients.go | 25 ------ ...resource_openstack_compute_instance_v2.go} | 79 +++++++++++-------- ... resource_openstack_compute_keypair_v2.go} | 36 +++++++-- ...resource_openstack_compute_secgroup_v2.go} | 54 +++++++++---- ...urce_openstack_compute_secgrouprule_v2.go} | 31 +++++--- ....go => resource_openstack_lb_member_v1.go} | 48 ++++++++--- ...go => resource_openstack_lb_monitor_v1.go} | 52 ++++++++---- ...ol.go => resource_openstack_lb_pool_v1.go} | 56 +++++++++---- ...vip.go => resource_openstack_lb_vip_v1.go} | 53 ++++++++----- ...source_openstack_networking_network_v2.go} | 47 ++++++++--- ...esource_openstack_networking_subnet_v2.go} | 57 ++++++++----- 11 files changed, 357 insertions(+), 181 deletions(-) delete mode 100644 builtin/providers/openstack/clients.go rename builtin/providers/openstack/{resource_openstack_compute_instance.go => resource_openstack_compute_instance_v2.go} (83%) rename builtin/providers/openstack/{resource_openstack_compute_keypair.go => resource_openstack_compute_keypair_v2.go} (57%) rename builtin/providers/openstack/{resource_openstack_compute_secgroup.go => resource_openstack_compute_secgroup_v2.go} (55%) rename builtin/providers/openstack/{resource_openstack_compute_secgrouprule.go => resource_openstack_compute_secgrouprule_v2.go} (73%) rename builtin/providers/openstack/{resource_openstack_lb_member.go => resource_openstack_lb_member_v1.go} (65%) rename builtin/providers/openstack/{resource_openstack_lb_monitor.go => resource_openstack_lb_monitor_v1.go} (78%) rename builtin/providers/openstack/{resource_openstack_lb_pool.go => resource_openstack_lb_pool_v1.go} (72%) rename builtin/providers/openstack/{resource_openstack_lb_vip.go => resource_openstack_lb_vip_v1.go} (80%) rename builtin/providers/openstack/{resource_openstack_networking_network.go => resource_openstack_networking_network_v2.go} (74%) rename builtin/providers/openstack/{resource_openstack_networking_subnet.go => resource_openstack_networking_subnet_v2.go} (82%) diff --git a/builtin/providers/openstack/clients.go b/builtin/providers/openstack/clients.go deleted file mode 100644 index b08804467..000000000 --- a/builtin/providers/openstack/clients.go +++ /dev/null @@ -1,25 +0,0 @@ -package openstack - -import ( - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" -) - -func newClient(c *Config, service, region string, version int) (*gophercloud.ServiceClient, error) { - var serviceClient *gophercloud.ServiceClient - switch service { - case "compute": - if version == 2 { - serviceClient, err = openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{ - Region: region, - }) - } - case "networking": - if version == 2 { - serviceClient, err = openstack.NewNetworkV2(c.osClient, gophercloud.EndpointOpts{ - Region: region, - }) - } - } - return serviceClient, err -} diff --git a/builtin/providers/openstack/resource_openstack_compute_instance.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go similarity index 83% rename from builtin/providers/openstack/resource_openstack_compute_instance.go rename to builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 5ef061c54..cf434566d 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" @@ -23,24 +24,27 @@ func resourceComputeInstance() *schema.Resource { Delete: resourceComputeInstanceDelete, Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, "name": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: false, }, - "image_ref": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: false, }, - "flavor_ref": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: false, }, - "security_groups": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -50,13 +54,11 @@ func resourceComputeInstance() *schema.Resource { return hashcode.String(v.(string)) }, }, - "availability_zone": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, - "networks": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -67,12 +69,10 @@ func resourceComputeInstance() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "port": &schema.Schema{ Type: schema.TypeString, Optional: true, }, - "fixed_ip": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -80,39 +80,33 @@ func resourceComputeInstance() *schema.Resource { }, }, }, - "metadata": &schema.Schema{ Type: schema.TypeMap, Optional: true, ForceNew: false, }, - "config_drive": &schema.Schema{ Type: schema.TypeBool, Optional: true, ForceNew: true, }, - "admin_pass": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "access_ip_v4": &schema.Schema{ Type: schema.TypeString, Computed: true, Optional: true, ForceNew: false, }, - "access_ip_v6": &schema.Schema{ Type: schema.TypeString, Computed: true, Optional: true, ForceNew: false, }, - "key_pair": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -124,7 +118,13 @@ func resourceComputeInstance() *schema.Resource { func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + var createOpts servers.CreateOptsBuilder @@ -148,7 +148,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err } log.Printf("[INFO] Requesting instance creation") - server, err := servers.Create(osClient, createOpts).Extract() + server, err := servers.Create(computeClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack server: %s", err) } @@ -166,7 +166,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err stateConf := &resource.StateChangeConf{ Pending: []string{"BUILD"}, Target: "ACTIVE", - Refresh: ServerStateRefreshFunc(osClient, server.ID), + Refresh: ServerStateRefreshFunc(computeClient, server.ID), Timeout: 10 * time.Minute, Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -184,9 +184,14 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } - server, err := servers.Get(osClient, d.Id()).Extract() + server, err := servers.Get(computeClient, d.Id()).Extract() if err != nil { return fmt.Errorf("Error retrieving OpenStack server: %s", err) } @@ -223,7 +228,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error d.Set("metadata", server.Metadata) var currentSG []string - err = secgroups.ListByServer(osClient, d.Id()).EachPage(func(page pagination.Page) (bool, error) { + err = secgroups.ListByServer(computeClient, d.Id()).EachPage(func(page pagination.Page) (bool, error) { secGrpList, err := secgroups.ExtractSecurityGroups(page) if err != nil { return false, fmt.Errorf("Error setting security groups for OpenStack server: %s", err) @@ -248,7 +253,12 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } var updateOpts servers.UpdateOpts if d.HasChange("name") { @@ -263,7 +273,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err log.Printf("[DEBUG] Updating Server %s with options: %+v", d.Id(), updateOpts) - _, err := servers.Update(osClient, d.Id(), updateOpts).Extract() + _, err = servers.Update(computeClient, d.Id(), updateOpts).Extract() if err != nil { return fmt.Errorf("Error updating OpenStack server: %s", err) } @@ -276,7 +286,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err metadataOpts[k] = v.(string) } - _, err := servers.UpdateMetadata(osClient, d.Id(), metadataOpts).Extract() + _, err := servers.UpdateMetadata(computeClient, d.Id(), metadataOpts).Extract() if err != nil { return fmt.Errorf("Error updating OpenStack server (%s) metadata: %s", d.Id(), err) } @@ -293,7 +303,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err log.Printf("[DEBUG] Security groups to remove: %v", secgroupsToRemove) for _, g := range secgroupsToAdd.List() { - err := secgroups.AddServerToGroup(osClient, d.Id(), g.(string)).ExtractErr() + err := secgroups.AddServerToGroup(computeClient, d.Id(), g.(string)).ExtractErr() if err != nil { return fmt.Errorf("Error adding security group to OpenStack server (%s): %s", d.Id(), err) } @@ -301,7 +311,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } for _, g := range secgroupsToRemove.List() { - err := secgroups.RemoveServerFromGroup(osClient, d.Id(), g.(string)).ExtractErr() + err := secgroups.RemoveServerFromGroup(computeClient, d.Id(), g.(string)).ExtractErr() if err != nil { return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) } @@ -311,7 +321,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err if d.HasChange("admin_pass") { if newPwd, ok := d.Get("admin_pass").(string); ok { - err := servers.ChangeAdminPassword(osClient, d.Id(), newPwd).ExtractErr() + err := servers.ChangeAdminPassword(computeClient, d.Id(), newPwd).ExtractErr() if err != nil { return fmt.Errorf("Error changing admin password of OpenStack server (%s): %s", d.Id(), err) } @@ -322,7 +332,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err resizeOpts := &servers.ResizeOpts{ FlavorRef: d.Get("flavor_ref").(string), } - err := servers.Resize(osClient, d.Id(), resizeOpts).ExtractErr() + err := servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr() if err != nil { return fmt.Errorf("Error resizing OpenStack server: %s", err) } @@ -333,7 +343,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err stateConf := &resource.StateChangeConf{ Pending: []string{"RESIZE"}, Target: "VERIFY_RESIZE", - Refresh: ServerStateRefreshFunc(osClient, d.Id()), + Refresh: ServerStateRefreshFunc(computeClient, d.Id()), Timeout: 3 * time.Minute, Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -346,7 +356,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err // Confirm resize. log.Printf("[DEBUG] Confirming resize") - err = servers.ConfirmResize(osClient, d.Id()).ExtractErr() + err = servers.ConfirmResize(computeClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error confirming resize of OpenStack server: %s", err) } @@ -354,7 +364,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err stateConf = &resource.StateChangeConf{ Pending: []string{"VERIFY_RESIZE"}, Target: "ACTIVE", - Refresh: ServerStateRefreshFunc(osClient, d.Id()), + Refresh: ServerStateRefreshFunc(computeClient, d.Id()), Timeout: 3 * time.Minute, Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -371,9 +381,14 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } - err := servers.Delete(osClient, d.Id()).ExtractErr() + err = servers.Delete(computeClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack server: %s", err) } @@ -383,7 +398,7 @@ func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) err stateConf := &resource.StateChangeConf{ Target: "", - Refresh: ServerStateRefreshFunc(osClient, d.Id()), + Refresh: ServerStateRefreshFunc(computeClient, d.Id()), Timeout: 10 * time.Minute, Delay: 10 * time.Second, MinTimeout: 3 * time.Second, diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair.go b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go similarity index 57% rename from builtin/providers/openstack/resource_openstack_compute_keypair.go rename to builtin/providers/openstack/resource_openstack_compute_keypair_v2.go index a8ca16e08..d27a348d4 100644 --- a/builtin/providers/openstack/resource_openstack_compute_keypair.go +++ b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" ) @@ -14,12 +16,17 @@ func resourceComputeKeypair() *schema.Resource { Delete: resourceComputeKeypairDelete, Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, "name": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "public_key": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -31,14 +38,19 @@ func resourceComputeKeypair() *schema.Resource { func resourceComputeKeypairCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } createOpts := keypairs.CreateOpts{ Name: d.Get("name").(string), PublicKey: d.Get("public_key").(string), } - kp, err := keypairs.Create(osClient, createOpts).Extract() + kp, err := keypairs.Create(computeClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack keypair: %s", err) } @@ -50,9 +62,14 @@ func resourceComputeKeypairCreate(d *schema.ResourceData, meta interface{}) erro func resourceComputeKeypairRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } - kp, err := keypairs.Get(osClient, d.Id()).Extract() + kp, err := keypairs.Get(computeClient, d.Id()).Extract() if err != nil { return fmt.Errorf("Error retrieving OpenStack keypair: %s", err) } @@ -65,9 +82,14 @@ func resourceComputeKeypairRead(d *schema.ResourceData, meta interface{}) error func resourceComputeKeypairDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } - err := keypairs.Delete(osClient, d.Id()).ExtractErr() + err = keypairs.Delete(computeClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack keypair: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go similarity index 55% rename from builtin/providers/openstack/resource_openstack_compute_secgroup.go rename to builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index f6471eb81..559f96cbf 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -5,6 +5,8 @@ import ( "log" "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" ) @@ -16,12 +18,17 @@ func resourceComputeSecGroup() *schema.Resource { Delete: resourceComputeSecGroupDelete, Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, "name": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: false, }, - "description": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -33,14 +40,19 @@ func resourceComputeSecGroup() *schema.Resource { func resourceComputeSecGroupCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } createOpts := secgroups.CreateOpts{ Name: d.Get("name").(string), Description: d.Get("description").(string), } - sg, err := secgroups.Create(osClient, createOpts).Extract() + sg, err := secgroups.Create(computeClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack security group: %s", err) } @@ -52,9 +64,14 @@ func resourceComputeSecGroupCreate(d *schema.ResourceData, meta interface{}) err func resourceComputeSecGroupRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } - sg, err := secgroups.Get(osClient, d.Id()).Extract() + sg, err := secgroups.Get(computeClient, d.Id()).Extract() if err != nil { return fmt.Errorf("Error retrieving OpenStack security group: %s", err) } @@ -67,7 +84,12 @@ func resourceComputeSecGroupRead(d *schema.ResourceData, meta interface{}) error func resourceComputeSecGroupUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } var updateOpts secgroups.UpdateOpts if d.HasChange("name") { @@ -77,14 +99,11 @@ func resourceComputeSecGroupUpdate(d *schema.ResourceData, meta interface{}) err updateOpts.Description = d.Get("description").(string) } - // If there's nothing to update, don't waste an HTTP call. - if updateOpts != (secgroups.UpdateOpts{}) { - log.Printf("[DEBUG] Updating Security Group (%s) with options: %+v", d.Id(), updateOpts) + log.Printf("[DEBUG] Updating Security Group (%s) with options: %+v", d.Id(), updateOpts) - _, err := secgroups.Update(osClient, d.Id(), updateOpts).Extract() - if err != nil { - return fmt.Errorf("Error updating OpenStack security group (%s): %s", d.Id(), err) - } + _, err = secgroups.Update(computeClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack security group (%s): %s", d.Id(), err) } return resourceComputeSecGroupRead(d, meta) @@ -92,9 +111,14 @@ func resourceComputeSecGroupUpdate(d *schema.ResourceData, meta interface{}) err func resourceComputeSecGroupDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } - err := secgroups.Delete(osClient, d.Id()).ExtractErr() + err = secgroups.Delete(computeClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack security group: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_compute_secgrouprule.go b/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go similarity index 73% rename from builtin/providers/openstack/resource_openstack_compute_secgrouprule.go rename to builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go index 1063e2df0..77c14f6f8 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgrouprule.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" ) @@ -14,36 +16,37 @@ func resourceComputeSecGroupRule() *schema.Resource { Delete: resourceComputeSecGroupRuleDelete, Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, "group_id": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "from_port": &schema.Schema{ Type: schema.TypeInt, Required: true, ForceNew: true, }, - "to_port": &schema.Schema{ Type: schema.TypeInt, Required: true, ForceNew: true, }, - "ip_protocol": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "cidr": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, - "from_group_id": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -55,7 +58,12 @@ func resourceComputeSecGroupRule() *schema.Resource { func resourceComputeSecGroupRuleCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } createOpts := secgroups.CreateRuleOpts{ ParentGroupID: d.Get("group_id").(string), @@ -66,7 +74,7 @@ func resourceComputeSecGroupRuleCreate(d *schema.ResourceData, meta interface{}) FromGroupID: d.Get("from_group_id").(string), } - sgr, err := secgroups.CreateRule(osClient, createOpts).Extract() + sgr, err := secgroups.CreateRule(computeClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack security group rule: %s", err) } @@ -88,9 +96,14 @@ func resourceComputeSecGroupRuleRead(d *schema.ResourceData, meta interface{}) e func resourceComputeSecGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.computeV2Client + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } - err := secgroups.DeleteRule(osClient, d.Id()).ExtractErr() + err = secgroups.DeleteRule(computeClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack security group rule: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_lb_member.go b/builtin/providers/openstack/resource_openstack_lb_member_v1.go similarity index 65% rename from builtin/providers/openstack/resource_openstack_lb_member.go rename to builtin/providers/openstack/resource_openstack_lb_member_v1.go index c628ee2b4..fdad89a00 100644 --- a/builtin/providers/openstack/resource_openstack_lb_member.go +++ b/builtin/providers/openstack/resource_openstack_lb_member_v1.go @@ -5,6 +5,8 @@ import ( "log" "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members" ) @@ -16,30 +18,32 @@ func resourceLBMember() *schema.Resource { Delete: resourceLBMemberDelete, Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, "tenant_id": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, - "address": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "port": &schema.Schema{ Type: schema.TypeInt, Required: true, ForceNew: true, }, - "pool_id": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "admin_state_up": &schema.Schema{ Type: schema.TypeBool, Required: true, @@ -51,7 +55,12 @@ func resourceLBMember() *schema.Resource { func resourceLBMemberCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } createOpts := members.CreateOpts{ //TenantID: d.Get("tenant_id").(string), @@ -61,7 +70,7 @@ func resourceLBMemberCreate(d *schema.ResourceData, meta interface{}) error { } log.Printf("[INFO] Requesting lb member creation") - p, err := members.Create(osClient, createOpts).Extract() + p, err := members.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack LB member: %s", err) } @@ -74,9 +83,14 @@ func resourceLBMemberCreate(d *schema.ResourceData, meta interface{}) error { func resourceLBMemberRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - p, err := members.Get(osClient, d.Id()).Extract() + p, err := members.Get(networkingClient, d.Id()).Extract() if err != nil { return fmt.Errorf("Error retrieving OpenStack LB Member: %s", err) } @@ -100,7 +114,12 @@ func resourceLBMemberRead(d *schema.ResourceData, meta interface{}) error { func resourceLBMemberUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } var updateOpts members.UpdateOpts if d.HasChange("admin_state_up") { @@ -109,7 +128,7 @@ func resourceLBMemberUpdate(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Updating OpenStack LB Member %s with options: %+v", d.Id(), updateOpts) - _, err := members.Update(osClient, d.Id(), updateOpts).Extract() + _, err = members.Update(networkingClient, d.Id(), updateOpts).Extract() if err != nil { return fmt.Errorf("Error updating OpenStack LB Member: %s", err) } @@ -119,9 +138,14 @@ func resourceLBMemberUpdate(d *schema.ResourceData, meta interface{}) error { func resourceLBMemberDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - err := members.Delete(osClient, d.Id()).ExtractErr() + err = members.Delete(networkingClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack LB Member: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_lb_monitor.go b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go similarity index 78% rename from builtin/providers/openstack/resource_openstack_lb_monitor.go rename to builtin/providers/openstack/resource_openstack_lb_monitor_v1.go index b2cb98641..cdf380319 100644 --- a/builtin/providers/openstack/resource_openstack_lb_monitor.go +++ b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go @@ -6,6 +6,8 @@ import ( "strconv" "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors" ) @@ -17,54 +19,52 @@ func resourceLBMonitor() *schema.Resource { Delete: resourceLBMonitorDelete, Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, "tenant_id": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, - "type": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "delay": &schema.Schema{ Type: schema.TypeInt, Required: true, ForceNew: false, }, - "timeout": &schema.Schema{ Type: schema.TypeInt, Required: true, ForceNew: false, }, - "max_retries": &schema.Schema{ Type: schema.TypeInt, Required: true, ForceNew: false, }, - "url_path": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "http_method": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "expected_codes": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "admin_state_up": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -76,7 +76,12 @@ func resourceLBMonitor() *schema.Resource { func resourceLBMonitorCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } createOpts := monitors.CreateOpts{ TenantID: d.Get("tenant_id").(string), @@ -99,7 +104,7 @@ func resourceLBMonitorCreate(d *schema.ResourceData, meta interface{}) error { } log.Printf("[INFO] Requesting lb monitor creation") - m, err := monitors.Create(osClient, createOpts).Extract() + m, err := monitors.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack LB Monitor: %s", err) } @@ -112,9 +117,14 @@ func resourceLBMonitorCreate(d *schema.ResourceData, meta interface{}) error { func resourceLBMonitorRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - m, err := monitors.Get(osClient, d.Id()).Extract() + m, err := monitors.Get(networkingClient, d.Id()).Extract() if err != nil { return fmt.Errorf("Error retrieving OpenStack LB Monitor: %s", err) } @@ -171,7 +181,12 @@ func resourceLBMonitorRead(d *schema.ResourceData, meta interface{}) error { func resourceLBMonitorUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } var updateOpts monitors.UpdateOpts if d.HasChange("delay") { @@ -205,7 +220,7 @@ func resourceLBMonitorUpdate(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Updating OpenStack LB Monitor %s with options: %+v", d.Id(), updateOpts) - _, err := monitors.Update(osClient, d.Id(), updateOpts).Extract() + _, err = monitors.Update(networkingClient, d.Id(), updateOpts).Extract() if err != nil { return fmt.Errorf("Error updating OpenStack LB Monitor: %s", err) } @@ -215,9 +230,14 @@ func resourceLBMonitorUpdate(d *schema.ResourceData, meta interface{}) error { func resourceLBMonitorDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - err := monitors.Delete(osClient, d.Id()).ExtractErr() + err = monitors.Delete(networkingClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack LB Monitor: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_lb_pool.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go similarity index 72% rename from builtin/providers/openstack/resource_openstack_lb_pool.go rename to builtin/providers/openstack/resource_openstack_lb_pool_v1.go index 0fb42463e..180bcb8fd 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go @@ -5,7 +5,9 @@ import ( "log" "github.com/hashicorp/terraform/helper/schema" - "github.com/jrperritt/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools" ) @@ -17,18 +19,22 @@ func resourceLBPool() *schema.Resource { Delete: resourceLBPoolDelete, Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, "name": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: false, }, - "protocol": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "subnet_id": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -40,13 +46,11 @@ func resourceLBPool() *schema.Resource { Required: true, ForceNew: false, }, - "tenant_id": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, - "monitor_ids": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -62,7 +66,12 @@ func resourceLBPool() *schema.Resource { func resourceLBPoolCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } createOpts := pools.CreateOpts{ Name: d.Get("name").(string), @@ -73,7 +82,7 @@ func resourceLBPoolCreate(d *schema.ResourceData, meta interface{}) error { } log.Printf("[INFO] Requesting lb pool creation") - p, err := pools.Create(osClient, createOpts).Extract() + p, err := pools.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack LB pool: %s", err) } @@ -83,7 +92,7 @@ func resourceLBPoolCreate(d *schema.ResourceData, meta interface{}) error { if mIDs := resourcePoolMonitorIDs(d); mIDs != nil { for _, mID := range mIDs { - _, err := pools.AssociateMonitor(osClient, p.ID, mID).Extract() + _, err := pools.AssociateMonitor(networkingClient, p.ID, mID).Extract() if err != nil { return fmt.Errorf("Error associating monitor (%s) with OpenStack LB pool (%s): %s", mID, p.ID, err) } @@ -95,9 +104,14 @@ func resourceLBPoolCreate(d *schema.ResourceData, meta interface{}) error { func resourceLBPoolRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - p, err := pools.Get(osClient, d.Id()).Extract() + p, err := pools.Get(networkingClient, d.Id()).Extract() if err != nil { return fmt.Errorf("Error retrieving OpenStack LB Pool: %s", err) } @@ -124,7 +138,12 @@ func resourceLBPoolRead(d *schema.ResourceData, meta interface{}) error { func resourceLBPoolUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } var updateOpts pools.UpdateOpts if d.HasChange("name") { @@ -136,7 +155,7 @@ func resourceLBPoolUpdate(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Updating OpenStack LB Pool %s with options: %+v", d.Id(), updateOpts) - _, err := pools.Update(osClient, d.Id(), updateOpts).Extract() + _, err = pools.Update(networkingClient, d.Id(), updateOpts).Extract() if err != nil { return fmt.Errorf("Error updating OpenStack LB Pool: %s", err) } @@ -152,7 +171,7 @@ func resourceLBPoolUpdate(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Monitors to remove: %v", monitorsToRemove) for _, m := range monitorsToAdd.List() { - _, err := pools.AssociateMonitor(osClient, d.Id(), m.(string)).Extract() + _, err := pools.AssociateMonitor(networkingClient, d.Id(), m.(string)).Extract() if err != nil { return fmt.Errorf("Error associating monitor (%s) with OpenStack server (%s): %s", m.(string), d.Id(), err) } @@ -160,7 +179,7 @@ func resourceLBPoolUpdate(d *schema.ResourceData, meta interface{}) error { } for _, m := range monitorsToRemove.List() { - _, err := pools.DisassociateMonitor(osClient, d.Id(), m.(string)).Extract() + _, err := pools.DisassociateMonitor(networkingClient, d.Id(), m.(string)).Extract() if err != nil { return fmt.Errorf("Error disassociating monitor (%s) from OpenStack server (%s): %s", m.(string), d.Id(), err) } @@ -173,9 +192,14 @@ func resourceLBPoolUpdate(d *schema.ResourceData, meta interface{}) error { func resourceLBPoolDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - err := pools.Delete(osClient, d.Id()).ExtractErr() + err = pools.Delete(networkingClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack LB Pool: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_lb_vip.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go similarity index 80% rename from builtin/providers/openstack/resource_openstack_lb_vip.go rename to builtin/providers/openstack/resource_openstack_lb_vip_v1.go index 405d01a19..ad95767cd 100644 --- a/builtin/providers/openstack/resource_openstack_lb_vip.go +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips" ) @@ -18,66 +19,62 @@ func resourceLBVip() *schema.Resource { Delete: resourceLBVipDelete, Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, "name": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: false, }, - "subnet_id": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "protocol": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "port": &schema.Schema{ Type: schema.TypeInt, Required: true, ForceNew: true, }, - "pool_id": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: false, }, - "tenant_id": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, - "address": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, - "description": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "persistence": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "conn_limit": &schema.Schema{ Type: schema.TypeInt, Optional: true, ForceNew: false, }, - "admin_state_up": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -89,7 +86,12 @@ func resourceLBVip() *schema.Resource { func resourceLBVipCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } createOpts := vips.CreateOpts{ Name: d.Get("name").(string), @@ -114,7 +116,7 @@ func resourceLBVipCreate(d *schema.ResourceData, meta interface{}) error { } log.Printf("[INFO] Requesting lb vip creation") - p, err := vips.Create(osClient, createOpts).Extract() + p, err := vips.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack LB VIP: %s", err) } @@ -127,9 +129,14 @@ func resourceLBVipCreate(d *schema.ResourceData, meta interface{}) error { func resourceLBVipRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - p, err := vips.Get(osClient, d.Id()).Extract() + p, err := vips.Get(networkingClient, d.Id()).Extract() if err != nil { return fmt.Errorf("Error retrieving OpenStack LB VIP: %s", err) } @@ -193,7 +200,12 @@ func resourceLBVipRead(d *schema.ResourceData, meta interface{}) error { func resourceLBVipUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } var updateOpts vips.UpdateOpts if d.HasChange("name") { @@ -224,7 +236,7 @@ func resourceLBVipUpdate(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Updating OpenStack LB VIP %s with options: %+v", d.Id(), updateOpts) - _, err := vips.Update(osClient, d.Id(), updateOpts).Extract() + _, err = vips.Update(networkingClient, d.Id(), updateOpts).Extract() if err != nil { return fmt.Errorf("Error updating OpenStack LB VIP: %s", err) } @@ -234,9 +246,14 @@ func resourceLBVipUpdate(d *schema.ResourceData, meta interface{}) error { func resourceLBVipDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - err := vips.Delete(osClient, d.Id()).ExtractErr() + err = vips.Delete(networkingClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack LB VIP: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_networking_network.go b/builtin/providers/openstack/resource_openstack_networking_network_v2.go similarity index 74% rename from builtin/providers/openstack/resource_openstack_networking_network.go rename to builtin/providers/openstack/resource_openstack_networking_network_v2.go index 8f262e369..3ce51fbb0 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2.go @@ -6,6 +6,8 @@ import ( "strconv" "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/networks" ) @@ -17,24 +19,27 @@ func resourceNetworkingNetwork() *schema.Resource { Delete: resourceNetworkingNetworkDelete, Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, "name": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "admin_state_up": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "shared": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "tenant_id": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -46,7 +51,12 @@ func resourceNetworkingNetwork() *schema.Resource { func resourceNetworkingNetworkCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } createOpts := networks.CreateOpts{ Name: d.Get("name").(string), @@ -72,7 +82,7 @@ func resourceNetworkingNetworkCreate(d *schema.ResourceData, meta interface{}) e } log.Printf("[INFO] Requesting network creation") - n, err := networks.Create(osClient, createOpts).Extract() + n, err := networks.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack Neutron network: %s", err) } @@ -85,9 +95,14 @@ func resourceNetworkingNetworkCreate(d *schema.ResourceData, meta interface{}) e func resourceNetworkingNetworkRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - n, err := networks.Get(osClient, d.Id()).Extract() + n, err := networks.Get(networkingClient, d.Id()).Extract() if err != nil { return fmt.Errorf("Error retrieving OpenStack Neutron Network: %s", err) } @@ -131,7 +146,12 @@ func resourceNetworkingNetworkRead(d *schema.ResourceData, meta interface{}) err func resourceNetworkingNetworkUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } var updateOpts networks.UpdateOpts if d.HasChange("name") { @@ -160,7 +180,7 @@ func resourceNetworkingNetworkUpdate(d *schema.ResourceData, meta interface{}) e log.Printf("[DEBUG] Updating Network %s with options: %+v", d.Id(), updateOpts) - _, err := networks.Update(osClient, d.Id(), updateOpts).Extract() + _, err = networks.Update(networkingClient, d.Id(), updateOpts).Extract() if err != nil { return fmt.Errorf("Error updating OpenStack Neutron Network: %s", err) } @@ -170,9 +190,14 @@ func resourceNetworkingNetworkUpdate(d *schema.ResourceData, meta interface{}) e func resourceNetworkingNetworkDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - err := networks.Delete(osClient, d.Id()).ExtractErr() + err = networks.Delete(networkingClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack Neutron Network: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet.go b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go similarity index 82% rename from builtin/providers/openstack/resource_openstack_networking_subnet.go rename to builtin/providers/openstack/resource_openstack_networking_subnet_v2.go index 0f52c0be2..ab337ec30 100644 --- a/builtin/providers/openstack/resource_openstack_networking_subnet.go +++ b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go @@ -6,7 +6,9 @@ import ( "strconv" "github.com/hashicorp/terraform/helper/schema" - "github.com/jrperritt/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/subnets" ) @@ -18,30 +20,32 @@ func resourceNetworkingSubnet() *schema.Resource { Delete: resourceNetworkingSubnetDelete, Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, "network_id": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "cidr": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, }, - "name": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "tenant_id": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, }, - "allocation_pools": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -52,7 +56,6 @@ func resourceNetworkingSubnet() *schema.Resource { Type: schema.TypeString, Required: true, }, - "end": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -60,25 +63,21 @@ func resourceNetworkingSubnet() *schema.Resource { }, }, }, - "gateway_ip": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "ip_version": &schema.Schema{ Type: schema.TypeInt, Required: true, ForceNew: true, }, - "enable_dhcp": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, }, - "dns_nameservers": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -88,7 +87,6 @@ func resourceNetworkingSubnet() *schema.Resource { return hashcode.String(v.(string)) }, }, - "host_routes": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -99,7 +97,6 @@ func resourceNetworkingSubnet() *schema.Resource { Type: schema.TypeString, Required: true, }, - "next_hop": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -113,7 +110,12 @@ func resourceNetworkingSubnet() *schema.Resource { func resourceNetworkingSubnetCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } createOpts := subnets.CreateOpts{ NetworkID: d.Get("network_id").(string), @@ -137,7 +139,7 @@ func resourceNetworkingSubnetCreate(d *schema.ResourceData, meta interface{}) er } log.Printf("[INFO] Requesting subnet creation") - s, err := subnets.Create(osClient, createOpts).Extract() + s, err := subnets.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack Neutron subnet: %s", err) } @@ -150,9 +152,14 @@ func resourceNetworkingSubnetCreate(d *schema.ResourceData, meta interface{}) er func resourceNetworkingSubnetRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - s, err := subnets.Get(osClient, d.Id()).Extract() + s, err := subnets.Get(networkingClient, d.Id()).Extract() if err != nil { return fmt.Errorf("Error retrieving OpenStack Neutron Subnet: %s", err) } @@ -212,7 +219,12 @@ func resourceNetworkingSubnetRead(d *schema.ResourceData, meta interface{}) erro func resourceNetworkingSubnetUpdate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } var updateOpts subnets.UpdateOpts @@ -245,7 +257,7 @@ func resourceNetworkingSubnetUpdate(d *schema.ResourceData, meta interface{}) er log.Printf("[DEBUG] Updating Subnet %s with options: %+v", d.Id(), updateOpts) - _, err := subnets.Update(osClient, d.Id(), updateOpts).Extract() + _, err = subnets.Update(networkingClient, d.Id(), updateOpts).Extract() if err != nil { return fmt.Errorf("Error updating OpenStack Neutron Subnet: %s", err) } @@ -255,9 +267,14 @@ func resourceNetworkingSubnetUpdate(d *schema.ResourceData, meta interface{}) er func resourceNetworkingSubnetDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - osClient := config.networkingV2Client + networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ + Region: d.Get("region").(string), + }) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } - err := subnets.Delete(osClient, d.Id()).ExtractErr() + err = subnets.Delete(networkingClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack Neutron Subnet: %s", err) } From dc99dd1f051a5001896c0ba36cb6f79f7fbc932f Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 11:09:27 -0700 Subject: [PATCH 177/295] add versioning to files --- builtin/providers/openstack/provider.go | 20 ++++----- .../resource_openstack_compute_instance_v2.go | 44 +++++++++---------- .../resource_openstack_compute_keypair_v2.go | 16 +++---- .../resource_openstack_compute_secgroup_v2.go | 22 +++++----- ...ource_openstack_compute_secgrouprule_v2.go | 16 +++---- .../resource_openstack_lb_member_v1.go | 22 +++++----- .../resource_openstack_lb_monitor_v1.go | 22 +++++----- .../resource_openstack_lb_pool_v1.go | 26 +++++------ .../openstack/resource_openstack_lb_vip_v1.go | 42 ++++++++++-------- ...esource_openstack_networking_network_v2.go | 22 +++++----- ...resource_openstack_networking_subnet_v2.go | 38 ++++++++-------- 11 files changed, 148 insertions(+), 142 deletions(-) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 8b3e37a58..27a6321fa 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -59,16 +59,16 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "openstack_compute_instance": resourceComputeInstance(), - "openstack_compute_keypair": resourceComputeKeypair(), - "openstack_compute_secgroup": resourceComputeSecGroup(), - "openstack_compute_secgrouprule": resourceComputeSecGroupRule(), - "openstack_lb_member": resourceLBMember(), - "openstack_lb_monitor": resourceLBMonitor(), - "openstack_lb_pool": resourceLBPool(), - "openstack_lb_vip": resourceLBVip(), - "openstack_networking_network": resourceNetworkingNetwork(), - "openstack_networking_subnet": resourceNetworkingSubnet(), + "openstack_compute_instance_v2": resourceComputeInstanceV2(), + "openstack_compute_keypair_v2": resourceComputeKeypairV2(), + "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), + "openstack_compute_secgrouprule_v2": resourceComputeSecGroupRuleV2(), + "openstack_lb_member_v1": resourceLBMemberV1(), + "openstack_lb_monitor_v1": resourceLBMonitorV1(), + "openstack_lb_pool_v1": resourceLBPoolV1(), + "openstack_lb_vip_v1": resourceLBVipV1(), + "openstack_networking_network_v2": resourceNetworkingNetworkV2(), + "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), }, ConfigureFunc: configureProvider, diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index cf434566d..38c3b9af7 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -16,12 +16,12 @@ import ( "github.com/rackspace/gophercloud/pagination" ) -func resourceComputeInstance() *schema.Resource { +func resourceComputeInstanceV2() *schema.Resource { return &schema.Resource{ - Create: resourceComputeInstanceCreate, - Read: resourceComputeInstanceRead, - Update: resourceComputeInstanceUpdate, - Delete: resourceComputeInstanceDelete, + Create: resourceComputeInstanceV2Create, + Read: resourceComputeInstanceV2Read, + Update: resourceComputeInstanceV2Update, + Delete: resourceComputeInstanceV2Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -116,7 +116,7 @@ func resourceComputeInstance() *schema.Resource { } } -func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error { +func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -132,10 +132,10 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err Name: d.Get("name").(string), ImageRef: d.Get("image_ref").(string), FlavorRef: d.Get("flavor_ref").(string), - SecurityGroups: resourceInstanceSecGroups(d), + SecurityGroups: resourceInstanceSecGroupsV2(d), AvailabilityZone: d.Get("availability_zone").(string), - Networks: resourceInstanceNetworks(d), - Metadata: resourceInstanceMetadata(d), + Networks: resourceInstanceNetworksV2(d), + Metadata: resourceInstanceMetadataV2(d), ConfigDrive: d.Get("config_drive").(bool), AdminPass: d.Get("admin_pass").(string), } @@ -166,7 +166,7 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err stateConf := &resource.StateChangeConf{ Pending: []string{"BUILD"}, Target: "ACTIVE", - Refresh: ServerStateRefreshFunc(computeClient, server.ID), + Refresh: ServerV2StateRefreshFunc(computeClient, server.ID), Timeout: 10 * time.Minute, Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -179,10 +179,10 @@ func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) err server.ID, err) } - return resourceComputeInstanceRead(d, meta) + return resourceComputeInstanceV2Read(d, meta) } -func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error { +func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -251,7 +251,7 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error return nil } -func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -343,7 +343,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err stateConf := &resource.StateChangeConf{ Pending: []string{"RESIZE"}, Target: "VERIFY_RESIZE", - Refresh: ServerStateRefreshFunc(computeClient, d.Id()), + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), Timeout: 3 * time.Minute, Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -364,7 +364,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err stateConf = &resource.StateChangeConf{ Pending: []string{"VERIFY_RESIZE"}, Target: "ACTIVE", - Refresh: ServerStateRefreshFunc(computeClient, d.Id()), + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), Timeout: 3 * time.Minute, Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -376,10 +376,10 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err } } - return resourceComputeInstanceRead(d, meta) + return resourceComputeInstanceV2Read(d, meta) } -func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error { +func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -398,7 +398,7 @@ func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) err stateConf := &resource.StateChangeConf{ Target: "", - Refresh: ServerStateRefreshFunc(computeClient, d.Id()), + Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), Timeout: 10 * time.Minute, Delay: 10 * time.Second, MinTimeout: 3 * time.Second, @@ -417,7 +417,7 @@ func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) err // ServerStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch // an OpenStack instance. -func ServerStateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc { +func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { s, err := servers.Get(client, instanceID).Extract() if err != nil { @@ -428,7 +428,7 @@ func ServerStateRefreshFunc(client *gophercloud.ServiceClient, instanceID string } } -func resourceInstanceSecGroups(d *schema.ResourceData) []string { +func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string { rawSecGroups := d.Get("security_groups").(*schema.Set) secgroups := make([]string, rawSecGroups.Len()) for i, raw := range rawSecGroups.List() { @@ -437,7 +437,7 @@ func resourceInstanceSecGroups(d *schema.ResourceData) []string { return secgroups } -func resourceInstanceNetworks(d *schema.ResourceData) []servers.Network { +func resourceInstanceNetworksV2(d *schema.ResourceData) []servers.Network { rawNetworks := d.Get("networks").([]interface{}) networks := make([]servers.Network, len(rawNetworks)) for i, raw := range rawNetworks { @@ -451,7 +451,7 @@ func resourceInstanceNetworks(d *schema.ResourceData) []servers.Network { return networks } -func resourceInstanceMetadata(d *schema.ResourceData) map[string]string { +func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string { m := make(map[string]string) for key, val := range d.Get("metadata").(map[string]interface{}) { m[key] = val.(string) diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go index d27a348d4..6fdaef631 100644 --- a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go @@ -9,11 +9,11 @@ import ( "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" ) -func resourceComputeKeypair() *schema.Resource { +func resourceComputeKeypairV2() *schema.Resource { return &schema.Resource{ - Create: resourceComputeKeypairCreate, - Read: resourceComputeKeypairRead, - Delete: resourceComputeKeypairDelete, + Create: resourceComputeKeypairV2Create, + Read: resourceComputeKeypairV2Read, + Delete: resourceComputeKeypairV2Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -36,7 +36,7 @@ func resourceComputeKeypair() *schema.Resource { } } -func resourceComputeKeypairCreate(d *schema.ResourceData, meta interface{}) error { +func resourceComputeKeypairV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -57,10 +57,10 @@ func resourceComputeKeypairCreate(d *schema.ResourceData, meta interface{}) erro d.SetId(kp.Name) - return resourceComputeKeypairRead(d, meta) + return resourceComputeKeypairV2Read(d, meta) } -func resourceComputeKeypairRead(d *schema.ResourceData, meta interface{}) error { +func resourceComputeKeypairV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -80,7 +80,7 @@ func resourceComputeKeypairRead(d *schema.ResourceData, meta interface{}) error return nil } -func resourceComputeKeypairDelete(d *schema.ResourceData, meta interface{}) error { +func resourceComputeKeypairV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index 559f96cbf..d6802e99d 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -10,12 +10,12 @@ import ( "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" ) -func resourceComputeSecGroup() *schema.Resource { +func resourceComputeSecGroupV2() *schema.Resource { return &schema.Resource{ - Create: resourceComputeSecGroupCreate, - Read: resourceComputeSecGroupRead, - Update: resourceComputeSecGroupUpdate, - Delete: resourceComputeSecGroupDelete, + Create: resourceComputeSecGroupV2Create, + Read: resourceComputeSecGroupV2Read, + Update: resourceComputeSecGroupV2Update, + Delete: resourceComputeSecGroupV2Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -38,7 +38,7 @@ func resourceComputeSecGroup() *schema.Resource { } } -func resourceComputeSecGroupCreate(d *schema.ResourceData, meta interface{}) error { +func resourceComputeSecGroupV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -59,10 +59,10 @@ func resourceComputeSecGroupCreate(d *schema.ResourceData, meta interface{}) err d.SetId(sg.ID) - return resourceComputeSecGroupRead(d, meta) + return resourceComputeSecGroupV2Read(d, meta) } -func resourceComputeSecGroupRead(d *schema.ResourceData, meta interface{}) error { +func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -82,7 +82,7 @@ func resourceComputeSecGroupRead(d *schema.ResourceData, meta interface{}) error return nil } -func resourceComputeSecGroupUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -106,10 +106,10 @@ func resourceComputeSecGroupUpdate(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Error updating OpenStack security group (%s): %s", d.Id(), err) } - return resourceComputeSecGroupRead(d, meta) + return resourceComputeSecGroupV2Read(d, meta) } -func resourceComputeSecGroupDelete(d *schema.ResourceData, meta interface{}) error { +func resourceComputeSecGroupV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), diff --git a/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go index 77c14f6f8..9a890d673 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go @@ -9,11 +9,11 @@ import ( "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" ) -func resourceComputeSecGroupRule() *schema.Resource { +func resourceComputeSecGroupRuleV2() *schema.Resource { return &schema.Resource{ - Create: resourceComputeSecGroupRuleCreate, - Read: resourceComputeSecGroupRuleRead, - Delete: resourceComputeSecGroupRuleDelete, + Create: resourceComputeSecGroupRuleV2Create, + Read: resourceComputeSecGroupRuleV2Read, + Delete: resourceComputeSecGroupRuleV2Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -56,7 +56,7 @@ func resourceComputeSecGroupRule() *schema.Resource { } } -func resourceComputeSecGroupRuleCreate(d *schema.ResourceData, meta interface{}) error { +func resourceComputeSecGroupRuleV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -87,14 +87,14 @@ func resourceComputeSecGroupRuleCreate(d *schema.ResourceData, meta interface{}) d.Set("cidr", sgr.IPRange.CIDR) d.Set("from_group_id", d.Get("from_group_id").(string)) - return resourceComputeSecGroupRuleRead(d, meta) + return resourceComputeSecGroupRuleV2Read(d, meta) } -func resourceComputeSecGroupRuleRead(d *schema.ResourceData, meta interface{}) error { +func resourceComputeSecGroupRuleV2Read(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceComputeSecGroupRuleDelete(d *schema.ResourceData, meta interface{}) error { +func resourceComputeSecGroupRuleV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), diff --git a/builtin/providers/openstack/resource_openstack_lb_member_v1.go b/builtin/providers/openstack/resource_openstack_lb_member_v1.go index fdad89a00..0a6f478b7 100644 --- a/builtin/providers/openstack/resource_openstack_lb_member_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_member_v1.go @@ -10,12 +10,12 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members" ) -func resourceLBMember() *schema.Resource { +func resourceLBMemberV1() *schema.Resource { return &schema.Resource{ - Create: resourceLBMemberCreate, - Read: resourceLBMemberRead, - Update: resourceLBMemberUpdate, - Delete: resourceLBMemberDelete, + Create: resourceLBMemberV1Create, + Read: resourceLBMemberV1Read, + Update: resourceLBMemberV1Update, + Delete: resourceLBMemberV1Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -53,7 +53,7 @@ func resourceLBMember() *schema.Resource { } } -func resourceLBMemberCreate(d *schema.ResourceData, meta interface{}) error { +func resourceLBMemberV1Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -78,10 +78,10 @@ func resourceLBMemberCreate(d *schema.ResourceData, meta interface{}) error { d.SetId(p.ID) - return resourceLBMemberRead(d, meta) + return resourceLBMemberV1Read(d, meta) } -func resourceLBMemberRead(d *schema.ResourceData, meta interface{}) error { +func resourceLBMemberV1Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -112,7 +112,7 @@ func resourceLBMemberRead(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceLBMemberUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceLBMemberV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -133,10 +133,10 @@ func resourceLBMemberUpdate(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error updating OpenStack LB Member: %s", err) } - return resourceLBMemberRead(d, meta) + return resourceLBMemberV1Read(d, meta) } -func resourceLBMemberDelete(d *schema.ResourceData, meta interface{}) error { +func resourceLBMemberV1Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), diff --git a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go index cdf380319..9d82e7e26 100644 --- a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go @@ -11,12 +11,12 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors" ) -func resourceLBMonitor() *schema.Resource { +func resourceLBMonitorV1() *schema.Resource { return &schema.Resource{ - Create: resourceLBMonitorCreate, - Read: resourceLBMonitorRead, - Update: resourceLBMonitorUpdate, - Delete: resourceLBMonitorDelete, + Create: resourceLBMonitorV1Create, + Read: resourceLBMonitorV1Read, + Update: resourceLBMonitorV1Update, + Delete: resourceLBMonitorV1Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -74,7 +74,7 @@ func resourceLBMonitor() *schema.Resource { } } -func resourceLBMonitorCreate(d *schema.ResourceData, meta interface{}) error { +func resourceLBMonitorV1Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -112,10 +112,10 @@ func resourceLBMonitorCreate(d *schema.ResourceData, meta interface{}) error { d.SetId(m.ID) - return resourceLBMonitorRead(d, meta) + return resourceLBMonitorV1Read(d, meta) } -func resourceLBMonitorRead(d *schema.ResourceData, meta interface{}) error { +func resourceLBMonitorV1Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -179,7 +179,7 @@ func resourceLBMonitorRead(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceLBMonitorUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceLBMonitorV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -225,10 +225,10 @@ func resourceLBMonitorUpdate(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error updating OpenStack LB Monitor: %s", err) } - return resourceLBMonitorRead(d, meta) + return resourceLBMonitorV1Read(d, meta) } -func resourceLBMonitorDelete(d *schema.ResourceData, meta interface{}) error { +func resourceLBMonitorV1Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go index 180bcb8fd..c5275c6e8 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go @@ -11,12 +11,12 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools" ) -func resourceLBPool() *schema.Resource { +func resourceLBPoolV1() *schema.Resource { return &schema.Resource{ - Create: resourceLBPoolCreate, - Read: resourceLBPoolRead, - Update: resourceLBPoolUpdate, - Delete: resourceLBPoolDelete, + Create: resourceLBPoolV1Create, + Read: resourceLBPoolV1Read, + Update: resourceLBPoolV1Update, + Delete: resourceLBPoolV1Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -64,7 +64,7 @@ func resourceLBPool() *schema.Resource { } } -func resourceLBPoolCreate(d *schema.ResourceData, meta interface{}) error { +func resourceLBPoolV1Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -90,7 +90,7 @@ func resourceLBPoolCreate(d *schema.ResourceData, meta interface{}) error { d.SetId(p.ID) - if mIDs := resourcePoolMonitorIDs(d); mIDs != nil { + if mIDs := resourcePoolMonitorIDsV1(d); mIDs != nil { for _, mID := range mIDs { _, err := pools.AssociateMonitor(networkingClient, p.ID, mID).Extract() if err != nil { @@ -99,10 +99,10 @@ func resourceLBPoolCreate(d *schema.ResourceData, meta interface{}) error { } } - return resourceLBPoolRead(d, meta) + return resourceLBPoolV1Read(d, meta) } -func resourceLBPoolRead(d *schema.ResourceData, meta interface{}) error { +func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -136,7 +136,7 @@ func resourceLBPoolRead(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceLBPoolUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceLBPoolV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -187,10 +187,10 @@ func resourceLBPoolUpdate(d *schema.ResourceData, meta interface{}) error { } } - return resourceLBPoolRead(d, meta) + return resourceLBPoolV1Read(d, meta) } -func resourceLBPoolDelete(d *schema.ResourceData, meta interface{}) error { +func resourceLBPoolV1Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -208,7 +208,7 @@ func resourceLBPoolDelete(d *schema.ResourceData, meta interface{}) error { return nil } -func resourcePoolMonitorIDs(d *schema.ResourceData) []string { +func resourcePoolMonitorIDsV1(d *schema.ResourceData) []string { mIDsRaw := d.Get("monitor_ids").(*schema.Set) mIDs := make([]string, mIDsRaw.Len()) for i, raw := range mIDsRaw.List() { diff --git a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go index ad95767cd..09c63bc41 100644 --- a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go @@ -11,12 +11,12 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips" ) -func resourceLBVip() *schema.Resource { +func resourceLBVipV1() *schema.Resource { return &schema.Resource{ - Create: resourceLBVipCreate, - Read: resourceLBVipRead, - Update: resourceLBVipUpdate, - Delete: resourceLBVipDelete, + Create: resourceLBVipV1Create, + Read: resourceLBVipV1Read, + Update: resourceLBVipV1Update, + Delete: resourceLBVipV1Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -84,7 +84,7 @@ func resourceLBVip() *schema.Resource { } } -func resourceLBVipCreate(d *schema.ResourceData, meta interface{}) error { +func resourceLBVipV1Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -102,7 +102,7 @@ func resourceLBVipCreate(d *schema.ResourceData, meta interface{}) error { TenantID: d.Get("tenant_id").(string), Address: d.Get("address").(string), Description: d.Get("description").(string), - Persistence: resourceVipPersistence(d), + Persistence: resourceVipPersistenceV1(d), ConnLimit: gophercloud.MaybeInt(d.Get("conn_limit").(int)), } @@ -124,10 +124,10 @@ func resourceLBVipCreate(d *schema.ResourceData, meta interface{}) error { d.SetId(p.ID) - return resourceLBVipRead(d, meta) + return resourceLBVipV1Read(d, meta) } -func resourceLBVipRead(d *schema.ResourceData, meta interface{}) error { +func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -198,7 +198,7 @@ func resourceLBVipRead(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceLBVipUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceLBVipV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -218,7 +218,7 @@ func resourceLBVipUpdate(d *schema.ResourceData, meta interface{}) error { updateOpts.Description = d.Get("description").(string) } if d.HasChange("persistence") { - updateOpts.Persistence = resourceVipPersistence(d) + updateOpts.Persistence = resourceVipPersistenceV1(d) } if d.HasChange("conn_limit") { updateOpts.ConnLimit = gophercloud.MaybeInt(d.Get("conn_limit").(int)) @@ -241,10 +241,10 @@ func resourceLBVipUpdate(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error updating OpenStack LB VIP: %s", err) } - return resourceLBVipRead(d, meta) + return resourceLBVipV1Read(d, meta) } -func resourceLBVipDelete(d *schema.ResourceData, meta interface{}) error { +func resourceLBVipV1Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -262,12 +262,18 @@ func resourceLBVipDelete(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceVipPersistence(d *schema.ResourceData) *vips.SessionPersistence { +func resourceVipPersistenceV1(d *schema.ResourceData) *vips.SessionPersistence { rawP := d.Get("persistence").(interface{}) rawMap := rawP.(map[string]interface{}) - p := vips.SessionPersistence{ - Type: rawMap["type"].(string), - CookieName: rawMap["cookie_name"].(string), + if rawMap != nil { + p := vips.SessionPersistence{} + if t, ok := rawMap["type"]; ok { + p.Type = t.(string) + } + if c, ok := rawMap["cookie_name"]; ok { + p.CookieName = c.(string) + } + return &p } - return &p + return nil } diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2.go b/builtin/providers/openstack/resource_openstack_networking_network_v2.go index 3ce51fbb0..3c33315c7 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2.go @@ -11,12 +11,12 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/networks" ) -func resourceNetworkingNetwork() *schema.Resource { +func resourceNetworkingNetworkV2() *schema.Resource { return &schema.Resource{ - Create: resourceNetworkingNetworkCreate, - Read: resourceNetworkingNetworkRead, - Update: resourceNetworkingNetworkUpdate, - Delete: resourceNetworkingNetworkDelete, + Create: resourceNetworkingNetworkV2Create, + Read: resourceNetworkingNetworkV2Read, + Update: resourceNetworkingNetworkV2Update, + Delete: resourceNetworkingNetworkV2Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -49,7 +49,7 @@ func resourceNetworkingNetwork() *schema.Resource { } } -func resourceNetworkingNetworkCreate(d *schema.ResourceData, meta interface{}) error { +func resourceNetworkingNetworkV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -90,10 +90,10 @@ func resourceNetworkingNetworkCreate(d *schema.ResourceData, meta interface{}) e d.SetId(n.ID) - return resourceNetworkingNetworkRead(d, meta) + return resourceNetworkingNetworkV2Read(d, meta) } -func resourceNetworkingNetworkRead(d *schema.ResourceData, meta interface{}) error { +func resourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -144,7 +144,7 @@ func resourceNetworkingNetworkRead(d *schema.ResourceData, meta interface{}) err return nil } -func resourceNetworkingNetworkUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceNetworkingNetworkV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -185,10 +185,10 @@ func resourceNetworkingNetworkUpdate(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error updating OpenStack Neutron Network: %s", err) } - return resourceNetworkingNetworkRead(d, meta) + return resourceNetworkingNetworkV2Read(d, meta) } -func resourceNetworkingNetworkDelete(d *schema.ResourceData, meta interface{}) error { +func resourceNetworkingNetworkV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go index ab337ec30..af25c4497 100644 --- a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go @@ -12,12 +12,12 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/subnets" ) -func resourceNetworkingSubnet() *schema.Resource { +func resourceNetworkingSubnetV2() *schema.Resource { return &schema.Resource{ - Create: resourceNetworkingSubnetCreate, - Read: resourceNetworkingSubnetRead, - Update: resourceNetworkingSubnetUpdate, - Delete: resourceNetworkingSubnetDelete, + Create: resourceNetworkingSubnetV2Create, + Read: resourceNetworkingSubnetV2Read, + Update: resourceNetworkingSubnetV2Update, + Delete: resourceNetworkingSubnetV2Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -108,7 +108,7 @@ func resourceNetworkingSubnet() *schema.Resource { } } -func resourceNetworkingSubnetCreate(d *schema.ResourceData, meta interface{}) error { +func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -122,11 +122,11 @@ func resourceNetworkingSubnetCreate(d *schema.ResourceData, meta interface{}) er CIDR: d.Get("cidr").(string), Name: d.Get("name").(string), TenantID: d.Get("tenant_id").(string), - AllocationPools: resourceSubnetAllocationPools(d), + AllocationPools: resourceSubnetAllocationPoolsV2(d), GatewayIP: d.Get("gateway_ip").(string), IPVersion: d.Get("ip_version").(int), - DNSNameservers: resourceSubnetDNSNameservers(d), - HostRoutes: resourceSubnetHostRoutes(d), + DNSNameservers: resourceSubnetDNSNameserversV2(d), + HostRoutes: resourceSubnetHostRoutesV2(d), } edRaw := d.Get("enable_dhcp").(string) @@ -147,10 +147,10 @@ func resourceNetworkingSubnetCreate(d *schema.ResourceData, meta interface{}) er d.SetId(s.ID) - return resourceNetworkingSubnetRead(d, meta) + return resourceNetworkingSubnetV2Read(d, meta) } -func resourceNetworkingSubnetRead(d *schema.ResourceData, meta interface{}) error { +func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -217,7 +217,7 @@ func resourceNetworkingSubnetRead(d *schema.ResourceData, meta interface{}) erro return nil } -func resourceNetworkingSubnetUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceNetworkingSubnetV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -237,11 +237,11 @@ func resourceNetworkingSubnetUpdate(d *schema.ResourceData, meta interface{}) er } if d.HasChange("dns_nameservers") { - updateOpts.DNSNameservers = resourceSubnetDNSNameservers(d) + updateOpts.DNSNameservers = resourceSubnetDNSNameserversV2(d) } if d.HasChange("host_routes") { - updateOpts.HostRoutes = resourceSubnetHostRoutes(d) + updateOpts.HostRoutes = resourceSubnetHostRoutesV2(d) } if d.HasChange("enable_dhcp") { @@ -262,10 +262,10 @@ func resourceNetworkingSubnetUpdate(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error updating OpenStack Neutron Subnet: %s", err) } - return resourceNetworkingSubnetRead(d, meta) + return resourceNetworkingSubnetV2Read(d, meta) } -func resourceNetworkingSubnetDelete(d *schema.ResourceData, meta interface{}) error { +func resourceNetworkingSubnetV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ Region: d.Get("region").(string), @@ -283,7 +283,7 @@ func resourceNetworkingSubnetDelete(d *schema.ResourceData, meta interface{}) er return nil } -func resourceSubnetAllocationPools(d *schema.ResourceData) []subnets.AllocationPool { +func resourceSubnetAllocationPoolsV2(d *schema.ResourceData) []subnets.AllocationPool { rawAPs := d.Get("allocation_pools").([]interface{}) aps := make([]subnets.AllocationPool, len(rawAPs)) for i, raw := range rawAPs { @@ -296,7 +296,7 @@ func resourceSubnetAllocationPools(d *schema.ResourceData) []subnets.AllocationP return aps } -func resourceSubnetDNSNameservers(d *schema.ResourceData) []string { +func resourceSubnetDNSNameserversV2(d *schema.ResourceData) []string { rawDNSN := d.Get("dns_nameservers").(*schema.Set) dnsn := make([]string, rawDNSN.Len()) for i, raw := range rawDNSN.List() { @@ -305,7 +305,7 @@ func resourceSubnetDNSNameservers(d *schema.ResourceData) []string { return dnsn } -func resourceSubnetHostRoutes(d *schema.ResourceData) []subnets.HostRoute { +func resourceSubnetHostRoutesV2(d *schema.ResourceData) []subnets.HostRoute { rawHR := d.Get("host_routes").([]interface{}) hr := make([]subnets.HostRoute, len(rawHR)) for i, raw := range rawHR { From 9b54c569cc2b20f0eb5a6a3c0f268d5ff2f6aa68 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 11:38:02 -0700 Subject: [PATCH 178/295] use 'Default' to forgo prompt --- builtin/providers/openstack/provider.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 27a6321fa..53f63f5d7 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -24,17 +24,17 @@ func Provider() terraform.ResourceProvider { "user_id": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: envDefaultFunc("OS_USERID"), + Default: "", }, "tenant_id": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: envDefaultFunc("OS_TENANT_ID"), + Default: "", }, "tenant_name": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: envDefaultFunc("OS_TENANT_NAME"), + Default: "", }, "password": &schema.Schema{ Type: schema.TypeString, @@ -44,17 +44,17 @@ func Provider() terraform.ResourceProvider { "api_key": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: envDefaultFunc("OS_API_KEY"), + Default: "", }, "domain_id": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: envDefaultFunc("OS_DOMAIN_ID"), + Default: "", }, "domain_name": &schema.Schema{ Type: schema.TypeString, Optional: true, - DefaultFunc: envDefaultFunc("OS_DOMAIN_NAME"), + Default: "", }, }, From 46a7949c9d01a4e3daab631fb029b0c09451ccd0 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 11:38:33 -0700 Subject: [PATCH 179/295] fix bug in SessionPersistence logic --- builtin/providers/openstack/resource_openstack_lb_vip_v1.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go index 09c63bc41..9c49b3276 100644 --- a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go @@ -66,7 +66,7 @@ func resourceLBVipV1() *schema.Resource { ForceNew: false, }, "persistence": &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeMap, Optional: true, ForceNew: false, }, @@ -265,7 +265,7 @@ func resourceLBVipV1Delete(d *schema.ResourceData, meta interface{}) error { func resourceVipPersistenceV1(d *schema.ResourceData) *vips.SessionPersistence { rawP := d.Get("persistence").(interface{}) rawMap := rawP.(map[string]interface{}) - if rawMap != nil { + if len(rawMap) != 0 { p := vips.SessionPersistence{} if t, ok := rawMap["type"]; ok { p.Type = t.(string) From 2214331b3cbdefffd324d6d23563b8aef170fe28 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 11:58:19 -0700 Subject: [PATCH 180/295] export 'region' from 'Read' operations --- .../openstack/resource_openstack_compute_instance_v2.go | 1 + .../openstack/resource_openstack_compute_keypair_v2.go | 1 + .../openstack/resource_openstack_compute_secgroup_v2.go | 1 + .../openstack/resource_openstack_compute_secgrouprule_v2.go | 1 + builtin/providers/openstack/resource_openstack_lb_member_v1.go | 1 + builtin/providers/openstack/resource_openstack_lb_monitor_v1.go | 1 + builtin/providers/openstack/resource_openstack_lb_pool_v1.go | 1 + builtin/providers/openstack/resource_openstack_lb_vip_v1.go | 1 + .../openstack/resource_openstack_networking_network_v2.go | 2 ++ .../openstack/resource_openstack_networking_subnet_v2.go | 1 + 10 files changed, 11 insertions(+) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 38c3b9af7..660679a0b 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -198,6 +198,7 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server) + d.Set("region", d.Get("region").(string)) d.Set("name", server.Name) d.Set("access_ip_v4", server.AccessIPv4) d.Set("access_ip_v6", server.AccessIPv6) diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go index 6fdaef631..e2d189a07 100644 --- a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go @@ -74,6 +74,7 @@ func resourceComputeKeypairV2Read(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error retrieving OpenStack keypair: %s", err) } + d.Set("region", d.Get("region").(string)) d.Set("name", kp.Name) d.Set("public_key", kp.PublicKey) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index d6802e99d..c89686575 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -76,6 +76,7 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("Error retrieving OpenStack security group: %s", err) } + d.Set("region", d.Get("region").(string)) d.Set("name", sg.Name) d.Set("description", sg.Description) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go index 9a890d673..fce40e621 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go @@ -80,6 +80,7 @@ func resourceComputeSecGroupRuleV2Create(d *schema.ResourceData, meta interface{ } d.SetId(sgr.ID) + d.Set("region", d.Get("region").(string)) d.Set("group_id", sgr.ParentGroupID) d.Set("from_port", sgr.FromPort) d.Set("to_port", sgr.ToPort) diff --git a/builtin/providers/openstack/resource_openstack_lb_member_v1.go b/builtin/providers/openstack/resource_openstack_lb_member_v1.go index 0a6f478b7..6fab4df80 100644 --- a/builtin/providers/openstack/resource_openstack_lb_member_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_member_v1.go @@ -97,6 +97,7 @@ func resourceLBMemberV1Read(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Retreived OpenStack LB Member %s: %+v", d.Id(), p) + d.Set("region", d.Get("region").(string)) d.Set("address", p.Address) d.Set("port", p.ProtocolPort) d.Set("pool_id", p.PoolID) diff --git a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go index 9d82e7e26..7883aa7dc 100644 --- a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go @@ -131,6 +131,7 @@ func resourceLBMonitorV1Read(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Retreived OpenStack LB Monitor %s: %+v", d.Id(), m) + d.Set("region", d.Get("region").(string)) d.Set("type", m.Type) d.Set("delay", m.Delay) d.Set("timeout", m.Timeout) diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go index c5275c6e8..219608eaf 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go @@ -118,6 +118,7 @@ func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Retreived OpenStack LB Pool %s: %+v", d.Id(), p) + d.Set("region", d.Get("region").(string)) d.Set("name", p.Name) d.Set("protocol", p.Protocol) d.Set("subnet_id", p.SubnetID) diff --git a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go index 9c49b3276..83ba829c5 100644 --- a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go @@ -143,6 +143,7 @@ func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Retreived OpenStack LB VIP %s: %+v", d.Id(), p) + d.Set("region", d.Get("region").(string)) d.Set("name", p.Name) d.Set("subnet_id", p.SubnetID) d.Set("protocol", p.Protocol) diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2.go b/builtin/providers/openstack/resource_openstack_networking_network_v2.go index 3c33315c7..ebb95efa4 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2.go @@ -109,6 +109,8 @@ func resourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) e log.Printf("[DEBUG] Retreived Network %s: %+v", d.Id(), n) + d.Set("region", d.Get("region").(string)) + if _, exists := d.GetOk("name"); exists { if d.HasChange("name") { d.Set("name", n.Name) diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go index af25c4497..d84f532db 100644 --- a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go @@ -166,6 +166,7 @@ func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) er log.Printf("[DEBUG] Retreived Subnet %s: %+v", d.Id(), s) + d.Set("region", d.Get("region").(string)) d.Set("newtork_id", s.NetworkID) d.Set("cidr", s.CIDR) d.Set("ip_version", s.IPVersion) From d51ee3111e4b7b53d03537530857b2f9ef64be93 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 12:59:38 -0700 Subject: [PATCH 181/295] always need both name and description when updating --- .../openstack/resource_openstack_compute_secgroup_v2.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index c89686575..5f61c267f 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -92,12 +92,9 @@ func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error creating OpenStack compute client: %s", err) } - var updateOpts secgroups.UpdateOpts - if d.HasChange("name") { - updateOpts.Name = d.Get("name").(string) - } - if d.HasChange("description") { - updateOpts.Description = d.Get("description").(string) + updateOpts := secgroups.UpdateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), } log.Printf("[DEBUG] Updating Security Group (%s) with options: %+v", d.Id(), updateOpts) From 5d2fe153c0149bed505bb6fd817a563ea803e59e Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 15:45:05 -0700 Subject: [PATCH 182/295] go fmt --- builtin/providers/openstack/provider.go | 36 +++++++++---------- .../resource_openstack_compute_instance_v2.go | 22 +++++------- .../resource_openstack_compute_keypair_v2.go | 1 - .../resource_openstack_compute_secgroup_v2.go | 3 +- .../resource_openstack_lb_pool_v1.go | 2 +- ...esource_openstack_networking_network_v2.go | 7 ++-- ...resource_openstack_networking_subnet_v2.go | 3 +- 7 files changed, 32 insertions(+), 42 deletions(-) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 53f63f5d7..48029afc7 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -22,19 +22,19 @@ func Provider() terraform.ResourceProvider { DefaultFunc: envDefaultFunc("OS_USERNAME"), }, "user_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "", + Type: schema.TypeString, + Optional: true, + Default: "", }, "tenant_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "", + Type: schema.TypeString, + Optional: true, + Default: "", }, "tenant_name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "", + Type: schema.TypeString, + Optional: true, + Default: "", }, "password": &schema.Schema{ Type: schema.TypeString, @@ -42,19 +42,19 @@ func Provider() terraform.ResourceProvider { DefaultFunc: envDefaultFunc("OS_PASSWORD"), }, "api_key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "", + Type: schema.TypeString, + Optional: true, + Default: "", }, "domain_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "", + Type: schema.TypeString, + Optional: true, + Default: "", }, "domain_name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "", + Type: schema.TypeString, + Optional: true, + Default: "", }, }, diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 660679a0b..5d87e2cb5 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -28,7 +28,6 @@ func resourceComputeInstanceV2() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - DefaultFunc: envDefaultFunc("OS_REGION_NAME"), }, "name": &schema.Schema{ Type: schema.TypeString, @@ -125,7 +124,6 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error creating OpenStack compute client: %s", err) } - var createOpts servers.CreateOptsBuilder serverCreateOpts := &servers.CreateOpts{ @@ -228,20 +226,16 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err d.Set("metadata", server.Metadata) - var currentSG []string err = secgroups.ListByServer(computeClient, d.Id()).EachPage(func(page pagination.Page) (bool, error) { secGrpList, err := secgroups.ExtractSecurityGroups(page) if err != nil { - return false, fmt.Errorf("Error setting security groups for OpenStack server: %s", err) + return false, fmt.Errorf("Error getting security groups for OpenStack server: %s", err) } - - for _, sg := range secGrpList { - currentSG = append(currentSG, sg.Name) + for i, sg := range secGrpList { + d.Set(fmt.Sprintf("security_groups.%d", i), sg.Name) } - return true, nil }) - d.Set("security_groups", currentSG) newFlavor, ok := server.Flavor["id"].(string) if !ok { @@ -272,11 +266,11 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e updateOpts.AccessIPv4 = d.Get("access_ip_v6").(string) } - log.Printf("[DEBUG] Updating Server %s with options: %+v", d.Id(), updateOpts) - - _, err = servers.Update(computeClient, d.Id(), updateOpts).Extract() - if err != nil { - return fmt.Errorf("Error updating OpenStack server: %s", err) + if updateOpts != (servers.UpdateOpts{}) { + _, err := servers.Update(computeClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack server: %s", err) + } } if d.HasChange("metadata") { diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go index e2d189a07..11cca95ba 100644 --- a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go @@ -20,7 +20,6 @@ func resourceComputeKeypairV2() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - DefaultFunc: envDefaultFunc("OS_REGION_NAME"), }, "name": &schema.Schema{ Type: schema.TypeString, diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index 5f61c267f..7dcbb48fb 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -22,7 +22,6 @@ func resourceComputeSecGroupV2() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - DefaultFunc: envDefaultFunc("OS_REGION_NAME"), }, "name": &schema.Schema{ Type: schema.TypeString, @@ -93,7 +92,7 @@ func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) e } updateOpts := secgroups.UpdateOpts{ - Name: d.Get("name").(string), + Name: d.Get("name").(string), Description: d.Get("description").(string), } diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go index 219608eaf..674110a8f 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go @@ -4,8 +4,8 @@ import ( "fmt" "log" - "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools" diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2.go b/builtin/providers/openstack/resource_openstack_networking_network_v2.go index ebb95efa4..3119baadd 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2.go @@ -20,10 +20,9 @@ func resourceNetworkingNetworkV2() *schema.Resource { Schema: map[string]*schema.Schema{ "region": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + Type: schema.TypeString, + Required: true, + ForceNew: true, }, "name": &schema.Schema{ Type: schema.TypeString, diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go index d84f532db..ecb2a4ea3 100644 --- a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go @@ -5,8 +5,8 @@ import ( "log" "strconv" - "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/subnets" @@ -24,7 +24,6 @@ func resourceNetworkingSubnetV2() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - DefaultFunc: envDefaultFunc("OS_REGION_NAME"), }, "network_id": &schema.Schema{ Type: schema.TypeString, From d86cb6be1c540db76adf496a012a26d1a88d138b Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 18:28:01 -0700 Subject: [PATCH 183/295] fix diff bug in 'Read' functions --- .../resource_openstack_compute_instance_v2.go | 8 +++-- .../resource_openstack_compute_keypair_v2.go | 1 + .../resource_openstack_compute_secgroup_v2.go | 1 + .../resource_openstack_lb_member_v1.go | 6 ++-- .../resource_openstack_lb_monitor_v1.go | 30 ++++++---------- .../resource_openstack_lb_pool_v1.go | 6 ++-- .../openstack/resource_openstack_lb_vip_v1.go | 34 ++++++------------- ...esource_openstack_networking_network_v2.go | 25 +++++--------- ...resource_openstack_networking_subnet_v2.go | 25 +++++--------- 9 files changed, 51 insertions(+), 85 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 5d87e2cb5..f6a922f43 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -28,6 +28,7 @@ func resourceComputeInstanceV2() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), }, "name": &schema.Schema{ Type: schema.TypeString, @@ -226,16 +227,19 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err d.Set("metadata", server.Metadata) + secGrpNum := 0 err = secgroups.ListByServer(computeClient, d.Id()).EachPage(func(page pagination.Page) (bool, error) { secGrpList, err := secgroups.ExtractSecurityGroups(page) if err != nil { return false, fmt.Errorf("Error getting security groups for OpenStack server: %s", err) } - for i, sg := range secGrpList { - d.Set(fmt.Sprintf("security_groups.%d", i), sg.Name) + for _, sg := range secGrpList { + d.Set(fmt.Sprintf("security_groups.%d", secGrpNum), sg.Name) + secGrpNum++ } return true, nil }) + d.Set("security_groups.#", secGrpNum) newFlavor, ok := server.Flavor["id"].(string) if !ok { diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go index 11cca95ba..e2d189a07 100644 --- a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go @@ -20,6 +20,7 @@ func resourceComputeKeypairV2() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), }, "name": &schema.Schema{ Type: schema.TypeString, diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index 7dcbb48fb..a79a6a88b 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -22,6 +22,7 @@ func resourceComputeSecGroupV2() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), }, "name": &schema.Schema{ Type: schema.TypeString, diff --git a/builtin/providers/openstack/resource_openstack_lb_member_v1.go b/builtin/providers/openstack/resource_openstack_lb_member_v1.go index 6fab4df80..2d615ed76 100644 --- a/builtin/providers/openstack/resource_openstack_lb_member_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_member_v1.go @@ -102,10 +102,8 @@ func resourceLBMemberV1Read(d *schema.ResourceData, meta interface{}) error { d.Set("port", p.ProtocolPort) d.Set("pool_id", p.PoolID) - if _, exists := d.GetOk("tenant_id"); exists { - if d.HasChange("tenant_id") { - d.Set("tenant_id", p.TenantID) - } + if t, exists := d.GetOk("tenant_id"); exists && t != "" { + d.Set("tenant_id", p.TenantID) } else { d.Set("tenant_id", "") } diff --git a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go index 7883aa7dc..f6c097d23 100644 --- a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go @@ -137,42 +137,32 @@ func resourceLBMonitorV1Read(d *schema.ResourceData, meta interface{}) error { d.Set("timeout", m.Timeout) d.Set("max_retries", m.MaxRetries) - if _, exists := d.GetOk("tenant_id"); exists { - if d.HasChange("tenant_id") { - d.Set("tenant_id", m.TenantID) - } + if t, exists := d.GetOk("tenant_id"); exists && t != "" { + d.Set("tenant_id", m.TenantID) } else { d.Set("tenant_id", "") } - if _, exists := d.GetOk("url_path"); exists { - if d.HasChange("url_path") { - d.Set("url_path", m.URLPath) - } + if t, exists := d.GetOk("url_path"); exists && t != "" { + d.Set("url_path", m.URLPath) } else { d.Set("url_path", "") } - if _, exists := d.GetOk("http_method"); exists { - if d.HasChange("http_method") { - d.Set("http_method", m.HTTPMethod) - } + if t, exists := d.GetOk("http_method"); exists && t != "" { + d.Set("http_method", m.HTTPMethod) } else { d.Set("http_method", "") } - if _, exists := d.GetOk("expected_codes"); exists { - if d.HasChange("expected_codes") { - d.Set("expected_codes", m.ExpectedCodes) - } + if t, exists := d.GetOk("expected_codes"); exists && t != "" { + d.Set("expected_codes", m.ExpectedCodes) } else { d.Set("expected_codes", "") } - if _, exists := d.GetOk("admin_state_up"); exists { - if d.HasChange("admin_state_up") { - d.Set("admin_state_up", strconv.FormatBool(m.AdminStateUp)) - } + if t, exists := d.GetOk("admin_state_up"); exists && t != "" { + d.Set("admin_state_up", strconv.FormatBool(m.AdminStateUp)) } else { d.Set("admin_state_up", "") } diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go index 674110a8f..6ca79a802 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go @@ -124,10 +124,8 @@ func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error { d.Set("subnet_id", p.SubnetID) d.Set("lb_method", p.LBMethod) - if _, exists := d.GetOk("tenant_id"); exists { - if d.HasChange("tenant_id") { - d.Set("tenant_id", p.TenantID) - } + if t, exists := d.GetOk("tenant_id"); exists && t != "" { + d.Set("tenant_id", p.TenantID) } else { d.Set("tenant_id", "") } diff --git a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go index 83ba829c5..748ed8d6a 100644 --- a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go @@ -150,48 +150,36 @@ func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error { d.Set("port", p.ProtocolPort) d.Set("pool_id", p.PoolID) - if _, exists := d.GetOk("tenant_id"); exists { - if d.HasChange("tenant_id") { - d.Set("tenant_id", p.TenantID) - } + if t, exists := d.GetOk("tenant_id"); exists && t != "" { + d.Set("tenant_id", p.TenantID) } else { d.Set("tenant_id", "") } - if _, exists := d.GetOk("address"); exists { - if d.HasChange("address") { - d.Set("address", p.Address) - } + if t, exists := d.GetOk("address"); exists && t != "" { + d.Set("address", p.Address) } else { d.Set("address", "") } - if _, exists := d.GetOk("description"); exists { - if d.HasChange("description") { - d.Set("description", p.Description) - } + if t, exists := d.GetOk("description"); exists && t != "" { + d.Set("description", p.Description) } else { d.Set("description", "") } - if _, exists := d.GetOk("persistence"); exists { - if d.HasChange("persistence") { + if t, exists := d.GetOk("persistence"); exists && t != "" { d.Set("persistence", p.Description) - } } - if _, exists := d.GetOk("conn_limit"); exists { - if d.HasChange("conn_limit") { - d.Set("conn_limit", p.ConnLimit) - } + if t, exists := d.GetOk("conn_limit"); exists && t != "" { + d.Set("conn_limit", p.ConnLimit) } else { d.Set("conn_limit", "") } - if _, exists := d.GetOk("admin_state_up"); exists { - if d.HasChange("admin_state_up") { - d.Set("admin_state_up", strconv.FormatBool(p.AdminStateUp)) - } + if t, exists := d.GetOk("admin_state_up"); exists && t != "" { + d.Set("admin_state_up", strconv.FormatBool(p.AdminStateUp)) } else { d.Set("admin_state_up", "") } diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2.go b/builtin/providers/openstack/resource_openstack_networking_network_v2.go index 3119baadd..92e02e031 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2.go @@ -23,6 +23,7 @@ func resourceNetworkingNetworkV2() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), }, "name": &schema.Schema{ Type: schema.TypeString, @@ -110,34 +111,26 @@ func resourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) e d.Set("region", d.Get("region").(string)) - if _, exists := d.GetOk("name"); exists { - if d.HasChange("name") { - d.Set("name", n.Name) - } + if t, exists := d.GetOk("name"); exists && t != ""{ + d.Set("name", n.Name) } else { d.Set("name", "") } - if _, exists := d.GetOk("admin_state_up"); exists { - if d.HasChange("admin_state_up") { - d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp)) - } + if t, exists := d.GetOk("admin_state_up"); exists && t != "" { + d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp)) } else { d.Set("admin_state_up", "") } - if _, exists := d.GetOk("shared"); exists { - if d.HasChange("shared") { - d.Set("shared", strconv.FormatBool(n.Shared)) - } + if t, exists := d.GetOk("shared"); exists && t != "" { + d.Set("shared", strconv.FormatBool(n.Shared)) } else { d.Set("shared", "") } - if _, exists := d.GetOk("tenant_id"); exists { - if d.HasChange("tenant_id") { - d.Set("tenant_id", n.TenantID) - } + if t, exists := d.GetOk("tenant_id"); exists && t != "" { + d.Set("tenant_id", n.TenantID) } else { d.Set("tenant_id", "") } diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go index ecb2a4ea3..0e72ce98d 100644 --- a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go @@ -24,6 +24,7 @@ func resourceNetworkingSubnetV2() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), }, "network_id": &schema.Schema{ Type: schema.TypeString, @@ -170,18 +171,14 @@ func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) er d.Set("cidr", s.CIDR) d.Set("ip_version", s.IPVersion) - if _, exists := d.GetOk("name"); exists { - if d.HasChange("name") { - d.Set("name", s.Name) - } + if t, exists := d.GetOk("name"); exists && t != "" { + d.Set("name", s.Name) } else { d.Set("name", "") } - if _, exists := d.GetOk("tenant_id"); exists { - if d.HasChange("tenant_id") { - d.Set("tenant_id", s.Name) - } + if t, exists := d.GetOk("tenant_id"); exists && t != "" { + d.Set("tenant_id", s.TenantID) } else { d.Set("tenant_id", "") } @@ -190,18 +187,14 @@ func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) er d.Set("allocation_pools", s.AllocationPools) } - if _, exists := d.GetOk("gateway_ip"); exists { - if d.HasChange("gateway_ip") { - d.Set("gateway_ip", s.Name) - } + if t, exists := d.GetOk("gateway_ip"); exists && t != "" { + d.Set("gateway_ip", s.GatewayIP) } else { d.Set("gateway_ip", "") } - if _, exists := d.GetOk("enable_dhcp"); exists { - if d.HasChange("enable_dhcp") { - d.Set("enable_dhcp", strconv.FormatBool(s.EnableDHCP)) - } + if t, exists := d.GetOk("enable_dhcp"); exists && t != "" { + d.Set("enable_dhcp", strconv.FormatBool(s.EnableDHCP)) } else { d.Set("enable_dhcp", "") } From d80a02c12d9710ec934429839af6dbd23c5707a6 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 19:26:53 -0700 Subject: [PATCH 184/295] update docs to reflect resource region and versioning --- .../providers/openstack/index.html.markdown | 27 ++++++++----- ...down => compute_instance_v2.html.markdown} | 17 +++++--- ...kdown => compute_keypair_v2.html.markdown} | 18 ++++++--- ...down => compute_secgroup_v2.html.markdown} | 18 ++++++--- ... => compute_secgrouprule_v2.html.markdown} | 22 ++++++---- ...ml.markdown => lb_member_v1.html.markdown} | 20 ++++++---- ...l.markdown => lb_monitor_v1.html.markdown} | 18 ++++++--- ...html.markdown => lb_pool_v1.html.markdown} | 18 ++++++--- ....html.markdown => lb_vip_v1.html.markdown} | 18 ++++++--- ...wn => networking_network_v2.html.markdown} | 18 ++++++--- ...own => networking_subnet_v2.html.markdown} | 22 ++++++---- website/source/layouts/openstack.erb | 40 +++++++++---------- 12 files changed, 161 insertions(+), 95 deletions(-) rename website/source/docs/providers/openstack/r/{compute_instance.html.markdown => compute_instance_v2.html.markdown} (81%) rename website/source/docs/providers/openstack/r/{compute_keypair.html.markdown => compute_keypair_v2.html.markdown} (59%) rename website/source/docs/providers/openstack/r/{compute_secgroup.html.markdown => compute_secgroup_v2.html.markdown} (50%) rename website/source/docs/providers/openstack/r/{compute_secgrouprule.html.markdown => compute_secgrouprule_v2.html.markdown} (67%) rename website/source/docs/providers/openstack/r/{lb_member.html.markdown => lb_member_v1.html.markdown} (65%) rename website/source/docs/providers/openstack/r/{lb_monitor.html.markdown => lb_monitor_v1.html.markdown} (80%) rename website/source/docs/providers/openstack/r/{lb_pool.html.markdown => lb_pool_v1.html.markdown} (72%) rename website/source/docs/providers/openstack/r/{lb_vip.html.markdown => lb_vip_v1.html.markdown} (82%) rename website/source/docs/providers/openstack/r/{networking_network.html.markdown => networking_network_v2.html.markdown} (63%) rename website/source/docs/providers/openstack/r/{networking_subnet.html.markdown => networking_subnet_v2.html.markdown} (78%) diff --git a/website/source/docs/providers/openstack/index.html.markdown b/website/source/docs/providers/openstack/index.html.markdown index 652523c5b..da6d1fb79 100644 --- a/website/source/docs/providers/openstack/index.html.markdown +++ b/website/source/docs/providers/openstack/index.html.markdown @@ -19,15 +19,14 @@ Use the navigation to the left to read about the available resources. ``` # Configure the OpenStack Provider provider "openstack" { - username = "admin" + user_name = "admin" tenant_name = "admin" password = "pwd" auth_url = "http://myauthurl:5000/v2.0" - region = "RegionOne" } # Create a web server -resource "openstack_compute_instance" "test-server" { +resource "openstack_compute_instance_v2" "test-server" { ... } ``` @@ -36,12 +35,20 @@ resource "openstack_compute_instance" "test-server" { The following arguments are supported: -* `username` - (Required) - -* `tenant_name` - (Required) - -* `password` - (Required) - * `auth_url` - (Required) -* `region` - (Required) +* `user_name` - (Optional; Required for Identity V2) + +* `user_id` - (Optional) + +* `password` - (Optional; Required if not using `api_key`) + +* `api_key` - (Optional; Required if not using `password`) + +* `domain_id` - (Optional) + +* `domain_name` - (Optional) + +* `tenant_id` - (Optional) + +* `tenant_name` - (Optional) diff --git a/website/source/docs/providers/openstack/r/compute_instance.html.markdown b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown similarity index 81% rename from website/source/docs/providers/openstack/r/compute_instance.html.markdown rename to website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown index 7de1cd1b9..4429174fb 100644 --- a/website/source/docs/providers/openstack/r/compute_instance.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown @@ -1,19 +1,19 @@ --- layout: "openstack" -page_title: "OpenStack: openstack_compute_instance" -sidebar_current: "docs-openstack-resource-compute-instance" +page_title: "OpenStack: openstack_compute_instance_v2" +sidebar_current: "docs-openstack-resource-compute-instance-v2" description: |- - Manages a VM instance resource within OpenStack. + Manages a V2 VM instance resource within OpenStack. --- -# openstack\_compute\_instance +# openstack\_compute\_instance_v2 -Manages a VM instance resource within OpenStack. +Manages a V2 VM instance resource within OpenStack. ## Example Usage ``` -resource "openstack_compute_instance" "test-server" { +resource "openstack_compute_instance_v2" "test-server" { name = "tf-test" image_ref = "ad091b52-742f-469e-8f3c-fd81cadf0743" flavor_ref = "3" @@ -29,6 +29,10 @@ resource "openstack_compute_instance" "test-server" { The following arguments are supported: +* `region` - (Required) The region in which to create the server instance. If + omitted, the `OS_REGION_NAME` environment variable is used. Changing this + creates a new server. + * `name` - (Required) A unique name for the resource. * `image_ref` - (Required) The image reference (ID) for the desired image for @@ -73,6 +77,7 @@ The `network` block supports: The following attributes are exported: +* `region` - See Argument Reference above. * `name` - See Argument Reference above. * `access_ip_v4` - See Argument Reference above. * `access_ip_v6` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/compute_keypair.html.markdown b/website/source/docs/providers/openstack/r/compute_keypair_v2.html.markdown similarity index 59% rename from website/source/docs/providers/openstack/r/compute_keypair.html.markdown rename to website/source/docs/providers/openstack/r/compute_keypair_v2.html.markdown index 15459a0ec..0c3beae27 100644 --- a/website/source/docs/providers/openstack/r/compute_keypair.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_keypair_v2.html.markdown @@ -1,19 +1,19 @@ --- layout: "openstack" -page_title: "OpenStack: openstack_compute_keypair" -sidebar_current: "docs-openstack-resource-compute-keypair" +page_title: "OpenStack: openstack_compute_keypair_v2" +sidebar_current: "docs-openstack-resource-compute-keypair-v2" description: |- - Manages a keypair resource within OpenStack. + Manages a V2 keypair resource within OpenStack. --- -# openstack\_compute\_keypair +# openstack\_compute\_keypair_v2 -Manages a keypair resource within OpenStack. +Manages a V2 keypair resource within OpenStack. ## Example Usage ``` -resource "openstack_compute_keypair" "test-keypair" { +resource "openstack_compute_keypair_v2" "test-keypair" { name = "my-keypair" public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAjpC1hwiOCCmKEWxJ4qzTTsJbKzndLotBCz5PcwtUnflmU+gHJtWMZKpuEGVi29h0A/+ydKek1O18k10Ff+4tyFjiHDQAnOfgWf7+b1yK+qDip3X1C0UPMbwHlTfSGWLGZqd9LvEFx9k3h/M+VtMvwR1lJ9LUyTAImnNjWG7TaIPmui30HvM2UiFEmqkr4ijq45MyX2+fLIePLRIF61p4whjHAQYufqyno3BS48icQb4p6iVEZPo4AE2o9oIyQvj2mx4dk5Y8CgSETOZTYDOR3rU2fZTRDRgPJDH9FWvQjF5tA0p3d9CoWWd2s6GKKbfoUIi8R/Db1BSPJwkqB" } @@ -23,6 +23,11 @@ resource "openstack_compute_keypair" "test-keypair" { The following arguments are supported: +* `region` - (Required) The region in which to obtain the V2 Compute client. + Keypairs are associated with accounts, but a Compute client is needed to + create one. If omitted, the `OS_REGION_NAME` environment variable is used. + Changing this creates a new keypair. + * `name` - (Required) A unique name for the keypair. Changing this creates a new keypair. @@ -33,5 +38,6 @@ The following arguments are supported: The following attributes are exported: +* `region` - See Argument Reference above. * `name` - See Argument Reference above. * `public_key` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/compute_secgroup.html.markdown b/website/source/docs/providers/openstack/r/compute_secgroup_v2.html.markdown similarity index 50% rename from website/source/docs/providers/openstack/r/compute_secgroup.html.markdown rename to website/source/docs/providers/openstack/r/compute_secgroup_v2.html.markdown index 27045b62b..86f546ed2 100644 --- a/website/source/docs/providers/openstack/r/compute_secgroup.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_secgroup_v2.html.markdown @@ -1,19 +1,19 @@ --- layout: "openstack" -page_title: "OpenStack: openstack_compute_secgroup" -sidebar_current: "docs-openstack-resource-compute-secgroup" +page_title: "OpenStack: openstack_compute_secgroup_v2" +sidebar_current: "docs-openstack-resource-compute-secgroup-2" description: |- - Manages a security group resource within OpenStack. + Manages a V2 security group resource within OpenStack. --- -# openstack\_compute\_secgroup +# openstack\_compute\_secgroup_v2 -Manages a security group resource within OpenStack. +Manages a V2 security group resource within OpenStack. ## Example Usage ``` -resource "openstack_compute_secgroup" "secgroup_1" { +resource "openstack_compute_secgroup_v2" "secgroup_1" { name = "my_secgroup" description = "my security group" } @@ -23,6 +23,11 @@ resource "openstack_compute_secgroup" "secgroup_1" { The following arguments are supported: +* `region` - (Required) The region in which to obtain the V2 Compute client. + A Compute client is needed to create a security group. If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + security group. + * `name` - (Required) A unique name for the security group. Changing this updates the `name` of an existing security group. @@ -33,5 +38,6 @@ The following arguments are supported: The following attributes are exported: +* `region` - See Argument Reference above. * `name` - See Argument Reference above. * `description` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/compute_secgrouprule.html.markdown b/website/source/docs/providers/openstack/r/compute_secgrouprule_v2.html.markdown similarity index 67% rename from website/source/docs/providers/openstack/r/compute_secgrouprule.html.markdown rename to website/source/docs/providers/openstack/r/compute_secgrouprule_v2.html.markdown index 60ffb347c..06385a6c8 100644 --- a/website/source/docs/providers/openstack/r/compute_secgrouprule.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_secgrouprule_v2.html.markdown @@ -1,25 +1,25 @@ --- layout: "openstack" -page_title: "OpenStack: openstack_compute_secgrouprule" -sidebar_current: "docs-openstack-resource-compute-secgrouprule" +page_title: "OpenStack: openstack_compute_secgrouprule_v2" +sidebar_current: "docs-openstack-resource-compute-secgrouprule-v2" description: |- - Manages a security group rule resource within OpenStack. + Manages a V2 security group rule resource within OpenStack. --- -# openstack\_compute\_secgrouprule +# openstack\_compute\_secgrouprule_v2 -Manages a security group rule resource within OpenStack. +Manages a V2 security group rule resource within OpenStack. ## Example Usage ``` -resource "openstack_compute_secgroup" "secgroup_1" { +resource "openstack_compute_secgroup_v2" "secgroup_1" { name = "my_secgroup" description = "my security group" } -resource "openstack_compute_secgrouprule" "secgrouprule_1" { - group_id = "${openstack_compute_secgroup.secgroup_1.id}" +resource "openstack_compute_secgrouprule_v2" "secgrouprule_1" { + group_id = "${openstack_compute_secgroup_v2.secgroup_1.id}" from_port = 22 to_port = 22 ip_protocol = "TCP" @@ -31,6 +31,11 @@ resource "openstack_compute_secgrouprule" "secgrouprule_1" { The following arguments are supported: +* `region` - (Required) The region in which to obtain the V2 Compute client. + A Compute client is needed to create a security group rule. If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + security group rule. + * `group_id` - (Required) The ID of the group to which this rule will be added. Changing this creates a new security group rule. @@ -55,6 +60,7 @@ The following arguments are supported: The following attributes are exported: +* `region` - See Argument Reference above. * `group_id` - See Argument Reference above. * `from_port` - See Argument Reference above. * `to_port` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/lb_member.html.markdown b/website/source/docs/providers/openstack/r/lb_member_v1.html.markdown similarity index 65% rename from website/source/docs/providers/openstack/r/lb_member.html.markdown rename to website/source/docs/providers/openstack/r/lb_member_v1.html.markdown index 0607ca840..2d1be74dc 100644 --- a/website/source/docs/providers/openstack/r/lb_member.html.markdown +++ b/website/source/docs/providers/openstack/r/lb_member_v1.html.markdown @@ -1,22 +1,22 @@ --- layout: "openstack" -page_title: "OpenStack: openstack_lb_member" -sidebar_current: "docs-openstack-resource-lb-member" +page_title: "OpenStack: openstack_lb_member_v1" +sidebar_current: "docs-openstack-resource-lb-member-v1" description: |- - Manages a load balancer member resource within OpenStack. + Manages a V1 load balancer member resource within OpenStack. --- -# openstack\_lb\_member +# openstack\_lb\_member_v1 -Manages a load balancer member resource within OpenStack. +Manages a V1 load balancer member resource within OpenStack. ## Example Usage ``` -resource "openstack_lb_member" "node_1" { +resource "openstack_lb_member_v1" "node_1" { address = "196.172.0.1" port = 80 - pool_id = "$12345" + pool_id = "12345" admin_state_up = true } ``` @@ -25,6 +25,11 @@ resource "openstack_lb_member" "node_1" { The following arguments are supported: +* `region` - (Required) The region in which to obtain the V2 Networking client. + A Networking client is needed to create an LB member. If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + LB member. + * `address` - (Required) The IP address of the member. Changing this creates a new member. @@ -45,6 +50,7 @@ The following arguments are supported: The following attributes are exported: +* `region` - See Argument Reference above. * `address` - See Argument Reference above. * `port` - See Argument Reference above. * `pool_id` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/lb_monitor.html.markdown b/website/source/docs/providers/openstack/r/lb_monitor_v1.html.markdown similarity index 80% rename from website/source/docs/providers/openstack/r/lb_monitor.html.markdown rename to website/source/docs/providers/openstack/r/lb_monitor_v1.html.markdown index 36b814274..cbf6b2b87 100644 --- a/website/source/docs/providers/openstack/r/lb_monitor.html.markdown +++ b/website/source/docs/providers/openstack/r/lb_monitor_v1.html.markdown @@ -1,19 +1,19 @@ --- layout: "openstack" -page_title: "OpenStack: openstack_lb_monitor" -sidebar_current: "docs-openstack-resource-lb-monitor" +page_title: "OpenStack: openstack_lb_monitor_v1" +sidebar_current: "docs-openstack-resource-lb-monitor-v1" description: |- - Manages a load balancer monitor resource within OpenStack. + Manages a V1 load balancer monitor resource within OpenStack. --- -# openstack\_lb\_monitor +# openstack\_lb\_monitor_v1 -Manages a load balancer monitor resource within OpenStack. +Manages a V1 load balancer monitor resource within OpenStack. ## Example Usage ``` -resource "openstack_lb_monitor" "monitor_1" { +resource "openstack_lb_monitor_v1" "monitor_1" { type = "PING" delay = 30 timeout = 5 @@ -26,6 +26,11 @@ resource "openstack_lb_monitor" "monitor_1" { The following arguments are supported: +* `region` - (Required) The region in which to obtain the V2 Networking client. + A Networking client is needed to create an LB monitor. If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + LB monitor. + * `type` - (Required) The type of probe, which is PING, TCP, HTTP, or HTTPS, that is sent by the monitor to verify the member state. Changing this creates a new monitor. @@ -65,6 +70,7 @@ The following arguments are supported: The following attributes are exported: +* `region` - See Argument Reference above. * `type` - See Argument Reference above. * `delay` - See Argument Reference above. * `timeout` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/lb_pool.html.markdown b/website/source/docs/providers/openstack/r/lb_pool_v1.html.markdown similarity index 72% rename from website/source/docs/providers/openstack/r/lb_pool.html.markdown rename to website/source/docs/providers/openstack/r/lb_pool_v1.html.markdown index 90e3ed0c9..f0de6917a 100644 --- a/website/source/docs/providers/openstack/r/lb_pool.html.markdown +++ b/website/source/docs/providers/openstack/r/lb_pool_v1.html.markdown @@ -1,19 +1,19 @@ --- layout: "openstack" -page_title: "OpenStack: openstack_lb_pool" -sidebar_current: "docs-openstack-resource-lb-pool" +page_title: "OpenStack: openstack_lb_pool_v1" +sidebar_current: "docs-openstack-resource-lb-pool-v1" description: |- - Manages a load balancer pool resource within OpenStack. + Manages a V1 load balancer pool resource within OpenStack. --- -# openstack\_lb\_pool +# openstack\_lb\_pool_v1 -Manages a load balancer pool resource within OpenStack. +Manages a V1 load balancer pool resource within OpenStack. ## Example Usage ``` -resource "openstack_lb_pool" "pool_1" { +resource "openstack_lb_pool_v1" "pool_1" { name = "tf_test_lb_pool" protocol = "HTTP" subnet_id = "12345" @@ -26,6 +26,11 @@ resource "openstack_lb_pool" "pool_1" { The following arguments are supported: +* `region` - (Required) The region in which to obtain the V2 Networking client. + A Networking client is needed to create an LB pool. If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + LB pool. + * `name` - (Required) The name of the pool. Changing this updates the name of the existing pool. @@ -50,6 +55,7 @@ The following arguments are supported: The following attributes are exported: +* `region` - See Argument Reference above. * `name` - See Argument Reference above. * `protocol` - See Argument Reference above. * `subnet_id` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/lb_vip.html.markdown b/website/source/docs/providers/openstack/r/lb_vip_v1.html.markdown similarity index 82% rename from website/source/docs/providers/openstack/r/lb_vip.html.markdown rename to website/source/docs/providers/openstack/r/lb_vip_v1.html.markdown index 0eddcaa5f..7a9bc3d4b 100644 --- a/website/source/docs/providers/openstack/r/lb_vip.html.markdown +++ b/website/source/docs/providers/openstack/r/lb_vip_v1.html.markdown @@ -1,19 +1,19 @@ --- layout: "openstack" -page_title: "OpenStack: openstack_lb_vip" -sidebar_current: "docs-openstack-resource-lb-vip" +page_title: "OpenStack: openstack_lb_vip_v1" +sidebar_current: "docs-openstack-resource-lb-vip-v1" description: |- - Manages a load balancer vip resource within OpenStack. + Manages a V1 load balancer vip resource within OpenStack. --- -# openstack\_lb\_vip +# openstack\_lb\_vip_v1 -Manages a load balancer vip resource within OpenStack. +Manages a V1 load balancer vip resource within OpenStack. ## Example Usage ``` -resource "openstack_lb_vip" "vip_1" { +resource "openstack_lb_vip_v1" "vip_1" { name = "tf_test_lb_vip" subnet_id = "12345" protocol = "HTTP" @@ -26,6 +26,11 @@ resource "openstack_lb_vip" "vip_1" { The following arguments are supported: +* `region` - (Required) The region in which to obtain the V2 Networking client. + A Networking client is needed to create a VIP. If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + VIP. + * `name` - (Required) The name of the vip. Changing this updates the name of the existing vip. @@ -76,6 +81,7 @@ The `persistence` block supports: The following attributes are exported: +* `region` - See Argument Reference above. * `name` - See Argument Reference above. * `subnet_id` - See Argument Reference above. * `protocol` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/networking_network.html.markdown b/website/source/docs/providers/openstack/r/networking_network_v2.html.markdown similarity index 63% rename from website/source/docs/providers/openstack/r/networking_network.html.markdown rename to website/source/docs/providers/openstack/r/networking_network_v2.html.markdown index eb765cac0..699cd1ae9 100644 --- a/website/source/docs/providers/openstack/r/networking_network.html.markdown +++ b/website/source/docs/providers/openstack/r/networking_network_v2.html.markdown @@ -1,19 +1,19 @@ --- layout: "openstack" -page_title: "OpenStack: openstack_networking_network" -sidebar_current: "docs-openstack-resource-networking-network" +page_title: "OpenStack: openstack_networking_network_v2" +sidebar_current: "docs-openstack-resource-networking-network-v2" description: |- - Manages a Neutron network resource within OpenStack. + Manages a V2 Neutron network resource within OpenStack. --- -# openstack\_networking\_network +# openstack\_networking\_network_v2 -Manages a Neutron network resource within OpenStack. +Manages a V2 Neutron network resource within OpenStack. ## Example Usage ``` -resource "openstack_networking_network" "network_1" { +resource "openstack_networking_network_v2" "network_1" { name = "tf_test_network" admin_state_up = "true" } @@ -23,6 +23,11 @@ resource "openstack_networking_network" "network_1" { The following arguments are supported: +* `region` - (Required) The region in which to obtain the V2 Networking client. + A Networking client is needed to create a Neutron network. If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + network. + * `name` - (Optional) The name of the network. Changing this updates the name of the existing network. @@ -41,6 +46,7 @@ The following arguments are supported: The following attributes are exported: +* `region` - See Argument Reference above. * `name` - See Argument Reference above. * `shared` - See Argument Reference above. * `tenant_id` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/networking_subnet.html.markdown b/website/source/docs/providers/openstack/r/networking_subnet_v2.html.markdown similarity index 78% rename from website/source/docs/providers/openstack/r/networking_subnet.html.markdown rename to website/source/docs/providers/openstack/r/networking_subnet_v2.html.markdown index f9ab4f6ed..a8243a817 100644 --- a/website/source/docs/providers/openstack/r/networking_subnet.html.markdown +++ b/website/source/docs/providers/openstack/r/networking_subnet_v2.html.markdown @@ -1,25 +1,25 @@ --- layout: "openstack" -page_title: "OpenStack: openstack_networking_subnet" -sidebar_current: "docs-openstack-resource-networking-subnet" +page_title: "OpenStack: openstack_networking_subnet_v2" +sidebar_current: "docs-openstack-resource-networking-subnet-v2" description: |- - Manages a Neutron subnet resource within OpenStack. + Manages a V2 Neutron subnet resource within OpenStack. --- -# openstack\_networking\_subnet +# openstack\_networking\_subnet_v2 -Manages a Neutron subnet resource within OpenStack. +Manages a V2 Neutron subnet resource within OpenStack. ## Example Usage ``` -resource "openstack_networking_network" "network_1" { +resource "openstack_networking_network_v2" "network_1" { name = "tf_test_network" admin_state_up = "true" } -resource "openstack_networking_subnet" "subnet_1" { - network_id = "${openstack_networking_network.network_1.id}" +resource "openstack_networking_subnet_v2" "subnet_1" { + network_id = "${openstack_networking_network_v2.network_1.id}" cidr = "192.168.199.0/24" ip_version = 4 } @@ -29,6 +29,11 @@ resource "openstack_networking_subnet" "subnet_1" { The following arguments are supported: +* `region` - (Required) The region in which to obtain the V2 Networking client. + A Networking client is needed to create a Neutron subnet. If omitted, the + `OS_REGION_NAME` environment variable is used. Changing this creates a new + subnet. + * `network_id` - (Required) The UUID of the parent network. Changing this creates a new subnet. @@ -80,6 +85,7 @@ The `host_routes` block supports: The following attributes are exported: +* `region` - See Argument Reference above. * `network_id` - See Argument Reference above. * `cidr` - See Argument Reference above. * `ip_version` - See Argument Reference above. diff --git a/website/source/layouts/openstack.erb b/website/source/layouts/openstack.erb index b7878887c..37b0c2a7c 100644 --- a/website/source/layouts/openstack.erb +++ b/website/source/layouts/openstack.erb @@ -13,35 +13,35 @@ > Resources From e6f3a192842806b509b343cbe7f5ae541fdd4935 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 22:33:34 -0700 Subject: [PATCH 185/295] add defaultFunc for 'tenant_name' --- builtin/providers/openstack/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 48029afc7..37a6e9b4c 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -34,7 +34,7 @@ func Provider() terraform.ResourceProvider { "tenant_name": &schema.Schema{ Type: schema.TypeString, Optional: true, - Default: "", + DefaultFunc: envDefaultFunc("OS_TENANT_NAME"), }, "password": &schema.Schema{ Type: schema.TypeString, From 59b5efc25aee9d7af40307789543dd40d29d9f1f Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 22:35:18 -0700 Subject: [PATCH 186/295] add defaultFuncs; fix bug with server createOpts --- .../openstack/resource_openstack_compute_instance_v2.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index f6a922f43..bacf3707e 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -39,11 +39,13 @@ func resourceComputeInstanceV2() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: false, + DefaultFunc: envDefaultFunc("OS_IMAGE_ID"), }, "flavor_ref": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: false, + DefaultFunc: envDefaultFunc("OS_FLAVOR_ID"), }, "security_groups": &schema.Schema{ Type: schema.TypeSet, @@ -127,7 +129,7 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e var createOpts servers.CreateOptsBuilder - serverCreateOpts := &servers.CreateOpts{ + createOpts = &servers.CreateOpts{ Name: d.Get("name").(string), ImageRef: d.Get("image_ref").(string), FlavorRef: d.Get("flavor_ref").(string), @@ -141,7 +143,7 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e if keyName, ok := d.Get("key_pair").(string); ok && keyName != "" { createOpts = &keypairs.CreateOptsExt{ - serverCreateOpts, + createOpts, keyName, } } From e278f852b34122de1d1e874026047c0823c00943 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 22:36:08 -0700 Subject: [PATCH 187/295] add image_ref and flavor_ref checks --- builtin/providers/openstack/provider_test.go | 32 ++++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/builtin/providers/openstack/provider_test.go b/builtin/providers/openstack/provider_test.go index 2819a4575..57257f5d0 100644 --- a/builtin/providers/openstack/provider_test.go +++ b/builtin/providers/openstack/provider_test.go @@ -8,6 +8,11 @@ import ( "github.com/hashicorp/terraform/terraform" ) + +var ( + OS_REGION_NAME = "" +) + var testAccProviders map[string]terraform.ResourceProvider var testAccProvider *schema.Provider @@ -29,23 +34,24 @@ func TestProvider_impl(t *testing.T) { } func testAccPreCheck(t *testing.T) { - if v := os.Getenv("OS_REGION_NAME"); v == "" { - t.Fatal("OS_REGION_NAME must be set for acceptance tests") - } - - if v := os.Getenv("OS_AUTH_URL"); v == "" { + v := os.Getenv("OS_AUTH_URL") + if v == "" { t.Fatal("OS_AUTH_URL must be set for acceptance tests") } - if v := os.Getenv("OS_USERNAME"); v == "" { - t.Fatal("OS_USERNAME must be set for acceptance tests") + v = os.Getenv("OS_REGION_NAME") + if v == "" { + t.Fatal("OS_REGION_NAME must be set for acceptance tests") + } + OS_REGION_NAME = v + + v = os.Getenv("OS_IMAGE_ID") + if v == "" { + t.Fatal("OS_IMAGE_ID must be set for acceptance tests") } - if v := os.Getenv("OS_TENANT_NAME"); v != "us-central1" { - t.Fatal("OS_TENANT_NAME must be set to us-central1 for acceptance tests") - } - - if v := os.Getenv("OS_PASSWORD"); v != "us-central1" { - t.Fatal("OS_PASSWORD must be set to us-central1 for acceptance tests") + v = os.Getenv("OS_FLAVOR_ID") + if v == "" { + t.Fatal("OS_FLAVOR_ID must be set for acceptance tests") } } From a707f8414cec65c19ec0867999bae6a08f83b6d5 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 26 Jan 2015 22:36:39 -0700 Subject: [PATCH 188/295] compute instance v2 acceptance tests --- ...urce_openstack_compute_instance_v2_test.go | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go new file mode 100644 index 000000000..4b73957d2 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go @@ -0,0 +1,123 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" + "github.com/rackspace/gophercloud/openstack/compute/v2/servers" +) + +func TestAccComputeV2Instance_basic(t *testing.T) { + var instance servers.Server + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeV2InstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeV2Instance_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance), + testAccCheckComputeV2InstanceMetadata(&instance, "foo", "bar"), + ), + }, + }, + }) +} + +func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: OS_REGION_NAME, + }) + if err != nil { + return fmt.Errorf("(testAccCheckComputeV2InstanceDestroy) Error creating OpenStack compute client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_compute_instance_v2" { + continue + } + + _, err := servers.Get(computeClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Instance still exists") + } + } + + return nil +} + +func testAccCheckComputeV2InstanceExists(t *testing.T, n string, instance *servers.Server) 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") + } + + osClient := testAccProvider.Meta().(*Config).osClient + computeClient, err := openstack.NewComputeV2(osClient, gophercloud.EndpointOpts{ + Region: OS_REGION_NAME, + }) + if err != nil { + return fmt.Errorf("(testAccCheckComputeV2InstanceExists) Error creating OpenStack compute client: %s", err) + } + + + found, err := servers.Get(computeClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Instance not found") + } + + *instance = *found + + return nil + } +} + +func testAccCheckComputeV2InstanceMetadata( + instance *servers.Server, k string, v string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if instance.Metadata == nil { + return fmt.Errorf("No metadata") + } + + for key, value := range instance.Metadata { + if k != key { + continue + } + + if v == value.(string) { + return nil + } + + return fmt.Errorf("Bad value for %s: %s", k, value) + } + + return fmt.Errorf("Metadata not found: %s", k) + } +} + +var testAccComputeV2Instance_basic = fmt.Sprintf(` + resource "openstack_compute_instance_v2" "foo" { + region = "%s" + name = "terraform-test" + metadata { + foo = "bar" + } + }`, + OS_REGION_NAME) From 17b137c972ad647581531d23ea061de391957c91 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Tue, 27 Jan 2015 10:19:11 -0700 Subject: [PATCH 189/295] go fmt --- builtin/providers/openstack/provider.go | 6 +++--- builtin/providers/openstack/provider_test.go | 1 - .../resource_openstack_compute_instance_v2.go | 12 ++++++------ .../resource_openstack_compute_instance_v2_test.go | 3 +-- .../openstack/resource_openstack_lb_vip_v1.go | 2 +- .../resource_openstack_networking_network_v2.go | 8 ++++---- 6 files changed, 15 insertions(+), 17 deletions(-) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 37a6e9b4c..b28c330bc 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -32,9 +32,9 @@ func Provider() terraform.ResourceProvider { Default: "", }, "tenant_name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - DefaultFunc: envDefaultFunc("OS_TENANT_NAME"), + Type: schema.TypeString, + Optional: true, + DefaultFunc: envDefaultFunc("OS_TENANT_NAME"), }, "password": &schema.Schema{ Type: schema.TypeString, diff --git a/builtin/providers/openstack/provider_test.go b/builtin/providers/openstack/provider_test.go index 57257f5d0..9e90bb4ea 100644 --- a/builtin/providers/openstack/provider_test.go +++ b/builtin/providers/openstack/provider_test.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform/terraform" ) - var ( OS_REGION_NAME = "" ) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index bacf3707e..8b23c5a87 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -36,15 +36,15 @@ func resourceComputeInstanceV2() *schema.Resource { ForceNew: false, }, "image_ref": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: false, + Type: schema.TypeString, + Required: true, + ForceNew: false, DefaultFunc: envDefaultFunc("OS_IMAGE_ID"), }, "flavor_ref": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: false, + Type: schema.TypeString, + Required: true, + ForceNew: false, DefaultFunc: envDefaultFunc("OS_FLAVOR_ID"), }, "security_groups": &schema.Schema{ diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go index 4b73957d2..ff2457d09 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go @@ -73,7 +73,6 @@ func testAccCheckComputeV2InstanceExists(t *testing.T, n string, instance *serve return fmt.Errorf("(testAccCheckComputeV2InstanceExists) Error creating OpenStack compute client: %s", err) } - found, err := servers.Get(computeClient, rs.Primary.ID).Extract() if err != nil { return err @@ -120,4 +119,4 @@ var testAccComputeV2Instance_basic = fmt.Sprintf(` foo = "bar" } }`, - OS_REGION_NAME) + OS_REGION_NAME) diff --git a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go index 748ed8d6a..eefe239be 100644 --- a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go @@ -169,7 +169,7 @@ func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error { } if t, exists := d.GetOk("persistence"); exists && t != "" { - d.Set("persistence", p.Description) + d.Set("persistence", p.Description) } if t, exists := d.GetOk("conn_limit"); exists && t != "" { diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2.go b/builtin/providers/openstack/resource_openstack_networking_network_v2.go index 92e02e031..e32db9697 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2.go @@ -20,9 +20,9 @@ func resourceNetworkingNetworkV2() *schema.Resource { Schema: map[string]*schema.Schema{ "region": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, DefaultFunc: envDefaultFunc("OS_REGION_NAME"), }, "name": &schema.Schema{ @@ -111,7 +111,7 @@ func resourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) e d.Set("region", d.Get("region").(string)) - if t, exists := d.GetOk("name"); exists && t != ""{ + if t, exists := d.GetOk("name"); exists && t != "" { d.Set("name", n.Name) } else { d.Set("name", "") From 3a6107d0ab5a877a69070d241ba1241f3864ace5 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Tue, 27 Jan 2015 10:19:45 -0700 Subject: [PATCH 190/295] keypairs v2 acceptance tests --- ...ource_openstack_compute_keypair_v2_test.go | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go new file mode 100644 index 000000000..19e5cf655 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go @@ -0,0 +1,96 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" +) + +func TestAccComputeV2Keypair_basic(t *testing.T) { + var keypair keypairs.KeyPair + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeV2KeypairDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeV2Keypair_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeV2KeypairExists(t, "openstack_compute_keypair_v2.foo", &keypair), + ), + }, + }, + }) +} + +func testAccCheckComputeV2KeypairDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ + Region: OS_REGION_NAME, + }) + if err != nil { + return fmt.Errorf("(testAccCheckComputeV2InstanceDestroy) Error creating OpenStack compute client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_compute_keypair_v2" { + continue + } + + _, err := keypairs.Get(computeClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Keypair still exists") + } + } + + return nil +} + +func testAccCheckComputeV2KeypairExists(t *testing.T, n string, kp *keypairs.KeyPair) 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") + } + + osClient := testAccProvider.Meta().(*Config).osClient + computeClient, err := openstack.NewComputeV2(osClient, gophercloud.EndpointOpts{ + Region: OS_REGION_NAME, + }) + if err != nil { + return fmt.Errorf("(testAccCheckComputeV2KeypairExists) Error creating OpenStack compute client: %s", err) + } + + found, err := keypairs.Get(computeClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Keypair not found") + } + + *kp = *found + + return nil + } +} + +var testAccComputeV2Keypair_basic = fmt.Sprintf(` + resource "openstack_compute_keypair_v2" "foo" { + region = "%s" + name = "test-keypair-tf" + public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAjpC1hwiOCCmKEWxJ4qzTTsJbKzndLo1BCz5PcwtUnflmU+gHJtWMZKpuEGVi29h0A/+ydKek1O18k10Ff+4tyFjiHDQAT9+OfgWf7+b1yK+qDip3X1C0UPMbwHlTfSGWLGZquwhvEFx9k3h/M+VtMvwR1lJ9LUyTAImnNjWG7TAIPmui30HvM2UiFEmqkr4ijq45MyX2+fLIePLRIFuu1p4whjHAQYufqyno3BS48icQb4p6iVEZPo4AE2o9oIyQvj2mx4dk5Y8CgSETOZTYDOR3rU2fZTRDRgPJDH9FWvQjF5tA0p3d9CoWWd2s6GKKbfoUIi8R/Db1BSPJwkqB jrp-hp-pc" + }`, + OS_REGION_NAME) From ea7c075273ef8a3237c67208f506115149245a04 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Thu, 29 Jan 2015 21:54:07 -0700 Subject: [PATCH 191/295] add security group rules ops to security groups file --- .../resource_openstack_compute_secgroup_v2.go | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index a79a6a88b..dd5aafd09 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -1,9 +1,11 @@ package openstack import ( + "bytes" "fmt" "log" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack" @@ -34,6 +36,40 @@ func resourceComputeSecGroupV2() *schema.Resource { Required: true, ForceNew: false, }, + "rules": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "from_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "to_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "ip_protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "cidr": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "from_group_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + Set: resourceSecGroupRuleHash, + }, }, } } @@ -59,6 +95,14 @@ func resourceComputeSecGroupV2Create(d *schema.ResourceData, meta interface{}) e d.SetId(sg.ID) + createRuleOptsList := resourceSecGroupRulesV2(d) + for _, createRuleOpts := range createRuleOptsList { + _, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack security group rule: %s", err) + } + } + return resourceComputeSecGroupV2Read(d, meta) } @@ -79,6 +123,7 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err d.Set("region", d.Get("region").(string)) d.Set("name", sg.Name) d.Set("description", sg.Description) + d.Set("rules", sg.Rules) return nil } @@ -104,6 +149,35 @@ func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error updating OpenStack security group (%s): %s", d.Id(), err) } + if d.HasChange("rules") { + oldSGRaw, newSGRaw := d.GetChange("rules") + oldSGRSet, newSGRSet := oldSGRaw.(*schema.Set), newSGRaw.(*schema.Set) + secgrouprulesToAdd := newSGRSet.Difference(oldSGRSet) + secgrouprulesToRemove := oldSGRSet.Difference(newSGRSet) + + log.Printf("[DEBUG] Security group rules to add: %v", secgrouprulesToAdd) + + log.Printf("[DEBUG] Security groups to remove: %v", secgrouprulesToRemove) + + for _, rawRule := range secgrouprulesToAdd.List() { + createRuleOpts := resourceSecGroupRuleV2(d, rawRule) + rule, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract() + if err != nil { + return fmt.Errorf("Error adding rule to OpenStack security group (%s): %s", d.Id(), err) + } + log.Printf("[DEBUG] Added rule (%s) to OpenStack security group (%s) ", rule.ID, d.Id()) + } + + for _, r := range secgrouprulesToRemove.List() { + rule := r.(secgroups.Rule) + err := secgroups.DeleteRule(computeClient, "").ExtractErr() + if err != nil { + return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err) + } + log.Printf("[DEBUG] Removed rule (%s) from OpenStack security group (%s)", rule.ID, d.Id()) + } + } + return resourceComputeSecGroupV2Read(d, meta) } @@ -123,3 +197,46 @@ func resourceComputeSecGroupV2Delete(d *schema.ResourceData, meta interface{}) e d.SetId("") return nil } + +func resourceSecGroupRuleHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) + buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) + buf.WriteString(fmt.Sprintf("%s-", m["ip_protocol"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["cidr"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["from_group_id"].(string))) + + return hashcode.String(buf.String()) +} + +func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts { + rawRules := (d.Get("rules")).(*schema.Set) + createRuleOptsList := make([]secgroups.CreateRuleOpts, rawRules.Len()) + for i, raw := range rawRules.List() { + rawMap := raw.(map[string]interface{}) + createRuleOptsList[i] = secgroups.CreateRuleOpts{ + ParentGroupID: d.Id(), + FromPort: rawMap["from_port"].(int), + ToPort: rawMap["to_port"].(int), + IPProtocol: rawMap["ip_protocol"].(string), + CIDR: rawMap["cidr"].(string), + FromGroupID: rawMap["from_group_id"].(string), + } + } + return createRuleOptsList +} + +func resourceSecGroupRuleV2(d *schema.ResourceData, raw interface{}) secgroups.CreateRuleOpts { + rawMap := raw.(map[string]interface{}) + createRuleOpts := secgroups.CreateRuleOpts{ + ParentGroupID: d.Id(), + FromPort: rawMap["from_port"].(int), + ToPort: rawMap["to_port"].(int), + IPProtocol: rawMap["ip_protocol"].(string), + CIDR: rawMap["cidr"].(string), + FromGroupID: rawMap["from_group_id"].(string), + } + + return createRuleOpts +} From c233c7f7f02f4664c3ec11f6ab42ca62612cec00 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Thu, 29 Jan 2015 21:56:40 -0700 Subject: [PATCH 192/295] fix typo in comment --- .../openstack/resource_openstack_compute_keypair_v2_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go index 19e5cf655..0456341df 100644 --- a/builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go @@ -36,7 +36,7 @@ func testAccCheckComputeV2KeypairDestroy(s *terraform.State) error { Region: OS_REGION_NAME, }) if err != nil { - return fmt.Errorf("(testAccCheckComputeV2InstanceDestroy) Error creating OpenStack compute client: %s", err) + return fmt.Errorf("(testAccCheckComputeV2KeypairDestroy) Error creating OpenStack compute client: %s", err) } for _, rs := range s.RootModule().Resources { From a2d2f927417bb1bb711a676bdaaacc397b0ba163 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Thu, 29 Jan 2015 21:57:14 -0700 Subject: [PATCH 193/295] remove security group rule file --- ...ource_openstack_compute_secgrouprule_v2.go | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go diff --git a/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go deleted file mode 100644 index fce40e621..000000000 --- a/builtin/providers/openstack/resource_openstack_compute_secgrouprule_v2.go +++ /dev/null @@ -1,113 +0,0 @@ -package openstack - -import ( - "fmt" - - "github.com/hashicorp/terraform/helper/schema" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" - "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" -) - -func resourceComputeSecGroupRuleV2() *schema.Resource { - return &schema.Resource{ - Create: resourceComputeSecGroupRuleV2Create, - Read: resourceComputeSecGroupRuleV2Read, - Delete: resourceComputeSecGroupRuleV2Delete, - - Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - DefaultFunc: envDefaultFunc("OS_REGION_NAME"), - }, - "group_id": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "from_port": &schema.Schema{ - Type: schema.TypeInt, - Required: true, - ForceNew: true, - }, - "to_port": &schema.Schema{ - Type: schema.TypeInt, - Required: true, - ForceNew: true, - }, - "ip_protocol": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "cidr": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "from_group_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - }, - } -} - -func resourceComputeSecGroupRuleV2Create(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) - if err != nil { - return fmt.Errorf("Error creating OpenStack compute client: %s", err) - } - - createOpts := secgroups.CreateRuleOpts{ - ParentGroupID: d.Get("group_id").(string), - FromPort: d.Get("from_port").(int), - ToPort: d.Get("to_port").(int), - IPProtocol: d.Get("ip_protocol").(string), - CIDR: d.Get("cidr").(string), - FromGroupID: d.Get("from_group_id").(string), - } - - sgr, err := secgroups.CreateRule(computeClient, createOpts).Extract() - if err != nil { - return fmt.Errorf("Error creating OpenStack security group rule: %s", err) - } - - d.SetId(sgr.ID) - d.Set("region", d.Get("region").(string)) - d.Set("group_id", sgr.ParentGroupID) - d.Set("from_port", sgr.FromPort) - d.Set("to_port", sgr.ToPort) - d.Set("ip_protocol", sgr.IPProtocol) - d.Set("cidr", sgr.IPRange.CIDR) - d.Set("from_group_id", d.Get("from_group_id").(string)) - - return resourceComputeSecGroupRuleV2Read(d, meta) -} - -func resourceComputeSecGroupRuleV2Read(d *schema.ResourceData, meta interface{}) error { - return nil -} - -func resourceComputeSecGroupRuleV2Delete(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) - if err != nil { - return fmt.Errorf("Error creating OpenStack compute client: %s", err) - } - - err = secgroups.DeleteRule(computeClient, d.Id()).ExtractErr() - if err != nil { - return fmt.Errorf("Error deleting OpenStack security group rule: %s", err) - } - d.SetId("") - return nil -} From 3627368fc040bfc39e7b38557931025071665e00 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Thu, 29 Jan 2015 22:09:59 -0700 Subject: [PATCH 194/295] remove security group rule option from provider --- builtin/providers/openstack/provider.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index b28c330bc..65496d534 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -59,16 +59,15 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "openstack_compute_instance_v2": resourceComputeInstanceV2(), - "openstack_compute_keypair_v2": resourceComputeKeypairV2(), - "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), - "openstack_compute_secgrouprule_v2": resourceComputeSecGroupRuleV2(), - "openstack_lb_member_v1": resourceLBMemberV1(), - "openstack_lb_monitor_v1": resourceLBMonitorV1(), - "openstack_lb_pool_v1": resourceLBPoolV1(), - "openstack_lb_vip_v1": resourceLBVipV1(), - "openstack_networking_network_v2": resourceNetworkingNetworkV2(), - "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), + "openstack_compute_instance_v2": resourceComputeInstanceV2(), + "openstack_compute_keypair_v2": resourceComputeKeypairV2(), + "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), + "openstack_lb_member_v1": resourceLBMemberV1(), + "openstack_lb_monitor_v1": resourceLBMonitorV1(), + "openstack_lb_pool_v1": resourceLBPoolV1(), + "openstack_lb_vip_v1": resourceLBVipV1(), + "openstack_networking_network_v2": resourceNetworkingNetworkV2(), + "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), }, ConfigureFunc: configureProvider, From 1aba665ad79dc426af72aa31799a09daa5d70c56 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 14:33:54 -0700 Subject: [PATCH 195/295] refactor service clients to *Config --- builtin/providers/openstack/config.go | 12 ++++++++++++ .../resource_openstack_compute_instance_v2.go | 17 ++++------------- .../resource_openstack_compute_keypair_v2.go | 14 +++----------- .../resource_openstack_compute_secgroup_v2.go | 18 ++++-------------- .../resource_openstack_lb_member_v1.go | 18 ++++-------------- .../resource_openstack_lb_monitor_v1.go | 18 ++++-------------- .../openstack/resource_openstack_lb_pool_v1.go | 18 ++++-------------- .../openstack/resource_openstack_lb_vip_v1.go | 17 ++++------------- ...resource_openstack_networking_network_v2.go | 18 ++++-------------- .../resource_openstack_networking_subnet_v2.go | 18 ++++-------------- 10 files changed, 47 insertions(+), 121 deletions(-) diff --git a/builtin/providers/openstack/config.go b/builtin/providers/openstack/config.go index 215817bdd..2e37c4fd0 100644 --- a/builtin/providers/openstack/config.go +++ b/builtin/providers/openstack/config.go @@ -41,3 +41,15 @@ func (c *Config) loadAndValidate() error { return nil } + +func (c *Config) computeV2Client(region string) (*gophercloud.ServiceClient, error) { + return openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{ + Region: region, + }) +} + +func (c *Config) networkingV2Client(region string) (*gophercloud.ServiceClient, error) { + return openstack.NewNetworkV2(c.osClient, gophercloud.EndpointOpts{ + Region: region, + }) +} diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 8b23c5a87..a2e66ca2e 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" @@ -120,9 +119,7 @@ func resourceComputeInstanceV2() *schema.Resource { func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + computeClient, err := config.computeV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) } @@ -185,9 +182,7 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + computeClient, err := config.computeV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) } @@ -254,9 +249,7 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + computeClient, err := config.computeV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) } @@ -382,9 +375,7 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + computeClient, err := config.computeV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go index e2d189a07..8cdc84913 100644 --- a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go @@ -4,8 +4,6 @@ import ( "fmt" "github.com/hashicorp/terraform/helper/schema" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" ) @@ -38,9 +36,7 @@ func resourceComputeKeypairV2() *schema.Resource { func resourceComputeKeypairV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + computeClient, err := config.computeV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) } @@ -62,9 +58,7 @@ func resourceComputeKeypairV2Create(d *schema.ResourceData, meta interface{}) er func resourceComputeKeypairV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + computeClient, err := config.computeV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) } @@ -83,9 +77,7 @@ func resourceComputeKeypairV2Read(d *schema.ResourceData, meta interface{}) erro func resourceComputeKeypairV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + computeClient, err := config.computeV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index dd5aafd09..f83ce84c7 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -7,8 +7,6 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" ) @@ -76,9 +74,7 @@ func resourceComputeSecGroupV2() *schema.Resource { func resourceComputeSecGroupV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + computeClient, err := config.computeV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) } @@ -108,9 +104,7 @@ func resourceComputeSecGroupV2Create(d *schema.ResourceData, meta interface{}) e func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + computeClient, err := config.computeV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) } @@ -130,9 +124,7 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + computeClient, err := config.computeV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) } @@ -183,9 +175,7 @@ func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) e func resourceComputeSecGroupV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + computeClient, err := config.computeV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_lb_member_v1.go b/builtin/providers/openstack/resource_openstack_lb_member_v1.go index 2d615ed76..9cadfef90 100644 --- a/builtin/providers/openstack/resource_openstack_lb_member_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_member_v1.go @@ -5,8 +5,6 @@ import ( "log" "github.com/hashicorp/terraform/helper/schema" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members" ) @@ -55,9 +53,7 @@ func resourceLBMemberV1() *schema.Resource { func resourceLBMemberV1Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -83,9 +79,7 @@ func resourceLBMemberV1Create(d *schema.ResourceData, meta interface{}) error { func resourceLBMemberV1Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -113,9 +107,7 @@ func resourceLBMemberV1Read(d *schema.ResourceData, meta interface{}) error { func resourceLBMemberV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -137,9 +129,7 @@ func resourceLBMemberV1Update(d *schema.ResourceData, meta interface{}) error { func resourceLBMemberV1Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go index f6c097d23..ca5ad7659 100644 --- a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go @@ -6,8 +6,6 @@ import ( "strconv" "github.com/hashicorp/terraform/helper/schema" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors" ) @@ -76,9 +74,7 @@ func resourceLBMonitorV1() *schema.Resource { func resourceLBMonitorV1Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -117,9 +113,7 @@ func resourceLBMonitorV1Create(d *schema.ResourceData, meta interface{}) error { func resourceLBMonitorV1Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -172,9 +166,7 @@ func resourceLBMonitorV1Read(d *schema.ResourceData, meta interface{}) error { func resourceLBMonitorV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -221,9 +213,7 @@ func resourceLBMonitorV1Update(d *schema.ResourceData, meta interface{}) error { func resourceLBMonitorV1Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go index 6ca79a802..3cadc2858 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go @@ -6,8 +6,6 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools" ) @@ -66,9 +64,7 @@ func resourceLBPoolV1() *schema.Resource { func resourceLBPoolV1Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -104,9 +100,7 @@ func resourceLBPoolV1Create(d *schema.ResourceData, meta interface{}) error { func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -137,9 +131,7 @@ func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error { func resourceLBPoolV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -191,9 +183,7 @@ func resourceLBPoolV1Update(d *schema.ResourceData, meta interface{}) error { func resourceLBPoolV1Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go index eefe239be..cef3977c8 100644 --- a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips" ) @@ -86,9 +85,7 @@ func resourceLBVipV1() *schema.Resource { func resourceLBVipV1Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -129,9 +126,7 @@ func resourceLBVipV1Create(d *schema.ResourceData, meta interface{}) error { func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -189,9 +184,7 @@ func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error { func resourceLBVipV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -235,9 +228,7 @@ func resourceLBVipV1Update(d *schema.ResourceData, meta interface{}) error { func resourceLBVipV1Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2.go b/builtin/providers/openstack/resource_openstack_networking_network_v2.go index e32db9697..fc420b2e9 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2.go @@ -6,8 +6,6 @@ import ( "strconv" "github.com/hashicorp/terraform/helper/schema" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/networks" ) @@ -51,9 +49,7 @@ func resourceNetworkingNetworkV2() *schema.Resource { func resourceNetworkingNetworkV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -95,9 +91,7 @@ func resourceNetworkingNetworkV2Create(d *schema.ResourceData, meta interface{}) func resourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -140,9 +134,7 @@ func resourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) e func resourceNetworkingNetworkV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -184,9 +176,7 @@ func resourceNetworkingNetworkV2Update(d *schema.ResourceData, meta interface{}) func resourceNetworkingNetworkV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go index 0e72ce98d..74a43e27c 100644 --- a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go @@ -7,8 +7,6 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/networking/v2/subnets" ) @@ -110,9 +108,7 @@ func resourceNetworkingSubnetV2() *schema.Resource { func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -152,9 +148,7 @@ func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{}) func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -212,9 +206,7 @@ func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) er func resourceNetworkingSubnetV2Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } @@ -260,9 +252,7 @@ func resourceNetworkingSubnetV2Update(d *schema.ResourceData, meta interface{}) func resourceNetworkingSubnetV2Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - networkingClient, err := openstack.NewNetworkV2(config.osClient, gophercloud.EndpointOpts{ - Region: d.Get("region").(string), - }) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } From 777c34cf7c4f6d791e5bd65ae013058d66c93246 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:19:42 -0700 Subject: [PATCH 196/295] move lb member ops into lb pool file --- builtin/providers/openstack/provider.go | 1 - .../resource_openstack_lb_member_v1.go | 144 ------------------ .../resource_openstack_lb_pool_v1.go | 127 +++++++++++++++ 3 files changed, 127 insertions(+), 145 deletions(-) delete mode 100644 builtin/providers/openstack/resource_openstack_lb_member_v1.go diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 65496d534..a53882f84 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -62,7 +62,6 @@ func Provider() terraform.ResourceProvider { "openstack_compute_instance_v2": resourceComputeInstanceV2(), "openstack_compute_keypair_v2": resourceComputeKeypairV2(), "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), - "openstack_lb_member_v1": resourceLBMemberV1(), "openstack_lb_monitor_v1": resourceLBMonitorV1(), "openstack_lb_pool_v1": resourceLBPoolV1(), "openstack_lb_vip_v1": resourceLBVipV1(), diff --git a/builtin/providers/openstack/resource_openstack_lb_member_v1.go b/builtin/providers/openstack/resource_openstack_lb_member_v1.go deleted file mode 100644 index 9cadfef90..000000000 --- a/builtin/providers/openstack/resource_openstack_lb_member_v1.go +++ /dev/null @@ -1,144 +0,0 @@ -package openstack - -import ( - "fmt" - "log" - - "github.com/hashicorp/terraform/helper/schema" - "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members" -) - -func resourceLBMemberV1() *schema.Resource { - return &schema.Resource{ - Create: resourceLBMemberV1Create, - Read: resourceLBMemberV1Read, - Update: resourceLBMemberV1Update, - Delete: resourceLBMemberV1Delete, - - Schema: map[string]*schema.Schema{ - "region": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - DefaultFunc: envDefaultFunc("OS_REGION_NAME"), - }, - "tenant_id": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "address": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "port": &schema.Schema{ - Type: schema.TypeInt, - Required: true, - ForceNew: true, - }, - "pool_id": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "admin_state_up": &schema.Schema{ - Type: schema.TypeBool, - Required: true, - ForceNew: false, - }, - }, - } -} - -func resourceLBMemberV1Create(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - networkingClient, err := config.networkingV2Client(d.Get("region").(string)) - if err != nil { - return fmt.Errorf("Error creating OpenStack networking client: %s", err) - } - - createOpts := members.CreateOpts{ - //TenantID: d.Get("tenant_id").(string), - Address: d.Get("address").(string), - ProtocolPort: d.Get("port").(int), - PoolID: d.Get("pool_id").(string), - } - - log.Printf("[INFO] Requesting lb member creation") - p, err := members.Create(networkingClient, createOpts).Extract() - if err != nil { - return fmt.Errorf("Error creating OpenStack LB member: %s", err) - } - log.Printf("[INFO] LB Member ID: %s", p.ID) - - d.SetId(p.ID) - - return resourceLBMemberV1Read(d, meta) -} - -func resourceLBMemberV1Read(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - networkingClient, err := config.networkingV2Client(d.Get("region").(string)) - if err != nil { - return fmt.Errorf("Error creating OpenStack networking client: %s", err) - } - - p, err := members.Get(networkingClient, d.Id()).Extract() - if err != nil { - return fmt.Errorf("Error retrieving OpenStack LB Member: %s", err) - } - - log.Printf("[DEBUG] Retreived OpenStack LB Member %s: %+v", d.Id(), p) - - d.Set("region", d.Get("region").(string)) - d.Set("address", p.Address) - d.Set("port", p.ProtocolPort) - d.Set("pool_id", p.PoolID) - - if t, exists := d.GetOk("tenant_id"); exists && t != "" { - d.Set("tenant_id", p.TenantID) - } else { - d.Set("tenant_id", "") - } - - return nil -} - -func resourceLBMemberV1Update(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - networkingClient, err := config.networkingV2Client(d.Get("region").(string)) - if err != nil { - return fmt.Errorf("Error creating OpenStack networking client: %s", err) - } - - var updateOpts members.UpdateOpts - if d.HasChange("admin_state_up") { - updateOpts.AdminStateUp = d.Get("admin_state_up").(bool) - } - - log.Printf("[DEBUG] Updating OpenStack LB Member %s with options: %+v", d.Id(), updateOpts) - - _, err = members.Update(networkingClient, d.Id(), updateOpts).Extract() - if err != nil { - return fmt.Errorf("Error updating OpenStack LB Member: %s", err) - } - - return resourceLBMemberV1Read(d, meta) -} - -func resourceLBMemberV1Delete(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - networkingClient, err := config.networkingV2Client(d.Get("region").(string)) - if err != nil { - return fmt.Errorf("Error creating OpenStack networking client: %s", err) - } - - err = members.Delete(networkingClient, d.Id()).ExtractErr() - if err != nil { - return fmt.Errorf("Error deleting OpenStack LB Member: %s", err) - } - - d.SetId("") - return nil -} diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go index 3cadc2858..a7b5abbaf 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go @@ -1,12 +1,15 @@ package openstack import ( + "bytes" "fmt" "log" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/members" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools" + "github.com/rackspace/gophercloud/pagination" ) func resourceLBPoolV1() *schema.Resource { @@ -49,6 +52,41 @@ func resourceLBPoolV1() *schema.Resource { Optional: true, ForceNew: true, }, + "member": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "admin_state_up": &schema.Schema{ + Type: schema.TypeBool, + Required: true, + ForceNew: false, + }, + }, + }, + Set: resourceLBMemberV1Hash, + }, "monitor_ids": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -95,6 +133,15 @@ func resourceLBPoolV1Create(d *schema.ResourceData, meta interface{}) error { } } + if memberOpts := resourcePoolMembersV1(d); memberOpts != nil { + for _, memberOpt := range memberOpts { + _, err := members.Create(networkingClient, memberOpt).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack LB member: %s", err) + } + } + } + return resourceLBPoolV1Read(d, meta) } @@ -125,6 +172,7 @@ func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error { } d.Set("monitor_ids", p.MonitorIDs) + d.Set("member_ids", p.MemberIDs) return nil } @@ -178,6 +226,49 @@ func resourceLBPoolV1Update(d *schema.ResourceData, meta interface{}) error { } } + if d.HasChange("member") { + oldMembersRaw, newMembersRaw := d.GetChange("member") + oldMembersSet, newMembersSet := oldMembersRaw.(*schema.Set), newMembersRaw.(*schema.Set) + membersToAdd := newMembersSet.Difference(oldMembersSet) + membersToRemove := oldMembersSet.Difference(newMembersSet) + + log.Printf("[DEBUG] Members to add: %v", membersToAdd) + + log.Printf("[DEBUG] Members to remove: %v", membersToRemove) + + for _, m := range membersToRemove.List() { + oldMember := resourcePoolMemberV1(d, m) + listOpts := members.ListOpts{ + PoolID: d.Id(), + Address: oldMember.Address, + ProtocolPort: oldMember.ProtocolPort, + } + err = members.List(networkingClient, listOpts).EachPage(func(page pagination.Page) (bool, error) { + extractedMembers, err := members.ExtractMembers(page) + if err != nil { + return false, err + } + for _, member := range extractedMembers { + err := members.Delete(networkingClient, member.ID).ExtractErr() + if err != nil { + return false, fmt.Errorf("Error deleting member (%s) from OpenStack LB pool (%s): %s", member.ID, d.Id(), err) + } + log.Printf("[DEBUG] Deleted member (%s) from pool (%s)", member.ID, d.Id()) + } + return true, nil + }) + } + + for _, m := range membersToAdd.List() { + createOpts := resourcePoolMemberV1(d, m) + newMember, err := members.Create(networkingClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating LB member: %s", err) + } + log.Printf("[DEBUG] Created member (%s) in OpenStack LB pool (%s)", newMember.ID, d.Id()) + } + } + return resourceLBPoolV1Read(d, meta) } @@ -205,3 +296,39 @@ func resourcePoolMonitorIDsV1(d *schema.ResourceData) []string { } return mIDs } + +func resourcePoolMembersV1(d *schema.ResourceData) []members.CreateOpts { + memberOptsRaw := (d.Get("member")).(*schema.Set) + memberOpts := make([]members.CreateOpts, memberOptsRaw.Len()) + for i, raw := range memberOptsRaw.List() { + rawMap := raw.(map[string]interface{}) + memberOpts[i] = members.CreateOpts{ + TenantID: rawMap["tenant_id"].(string), + Address: rawMap["address"].(string), + ProtocolPort: rawMap["port"].(int), + PoolID: d.Id(), + } + } + return memberOpts +} + +func resourcePoolMemberV1(d *schema.ResourceData, raw interface{}) members.CreateOpts { + rawMap := raw.(map[string]interface{}) + return members.CreateOpts{ + TenantID: rawMap["tenant_id"].(string), + Address: rawMap["address"].(string), + ProtocolPort: rawMap["port"].(int), + PoolID: d.Id(), + } +} + +func resourceLBMemberV1Hash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["region"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["tenant_id"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["address"].(string))) + buf.WriteString(fmt.Sprintf("%d-", m["port"].(int))) + + return hashcode.String(buf.String()) +} From 33d62bbdbffd41db611288e7bdf2ee2ff89bd746 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:21:57 -0700 Subject: [PATCH 197/295] 'networks' -> 'network' --- .../openstack/resource_openstack_compute_instance_v2.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index a2e66ca2e..8059dec1b 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -60,7 +60,7 @@ func resourceComputeInstanceV2() *schema.Resource { Optional: true, ForceNew: true, }, - "networks": &schema.Schema{ + "network": &schema.Schema{ Type: schema.TypeList, Optional: true, ForceNew: true, @@ -430,7 +430,7 @@ func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string { } func resourceInstanceNetworksV2(d *schema.ResourceData) []servers.Network { - rawNetworks := d.Get("networks").([]interface{}) + rawNetworks := d.Get("network").([]interface{}) networks := make([]servers.Network, len(rawNetworks)) for i, raw := range rawNetworks { rawMap := raw.(map[string]interface{}) From 6b2f2df04289b8ff87ae022c54e87b58bc9dccf2 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:22:19 -0700 Subject: [PATCH 198/295] 'rules' -> 'rule' --- .../resource_openstack_compute_secgroup_v2.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index f83ce84c7..f1d9ae714 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -34,7 +34,7 @@ func resourceComputeSecGroupV2() *schema.Resource { Required: true, ForceNew: false, }, - "rules": &schema.Schema{ + "rule": &schema.Schema{ Type: schema.TypeSet, Optional: true, Elem: &schema.Resource{ @@ -66,7 +66,7 @@ func resourceComputeSecGroupV2() *schema.Resource { }, }, }, - Set: resourceSecGroupRuleHash, + Set: resourceSecGroupRuleV2Hash, }, }, } @@ -117,7 +117,7 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err d.Set("region", d.Get("region").(string)) d.Set("name", sg.Name) d.Set("description", sg.Description) - d.Set("rules", sg.Rules) + d.Set("rule", sg.Rules) return nil } @@ -141,8 +141,8 @@ func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error updating OpenStack security group (%s): %s", d.Id(), err) } - if d.HasChange("rules") { - oldSGRaw, newSGRaw := d.GetChange("rules") + if d.HasChange("rule") { + oldSGRaw, newSGRaw := d.GetChange("rule") oldSGRSet, newSGRSet := oldSGRaw.(*schema.Set), newSGRaw.(*schema.Set) secgrouprulesToAdd := newSGRSet.Difference(oldSGRSet) secgrouprulesToRemove := oldSGRSet.Difference(newSGRSet) @@ -188,7 +188,7 @@ func resourceComputeSecGroupV2Delete(d *schema.ResourceData, meta interface{}) e return nil } -func resourceSecGroupRuleHash(v interface{}) int { +func resourceSecGroupRuleV2Hash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) @@ -201,7 +201,7 @@ func resourceSecGroupRuleHash(v interface{}) int { } func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts { - rawRules := (d.Get("rules")).(*schema.Set) + rawRules := (d.Get("rule")).(*schema.Set) createRuleOptsList := make([]secgroups.CreateRuleOpts, rawRules.Len()) for i, raw := range rawRules.List() { rawMap := raw.(map[string]interface{}) @@ -219,7 +219,7 @@ func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts func resourceSecGroupRuleV2(d *schema.ResourceData, raw interface{}) secgroups.CreateRuleOpts { rawMap := raw.(map[string]interface{}) - createRuleOpts := secgroups.CreateRuleOpts{ + return secgroups.CreateRuleOpts{ ParentGroupID: d.Id(), FromPort: rawMap["from_port"].(int), ToPort: rawMap["to_port"].(int), @@ -227,6 +227,4 @@ func resourceSecGroupRuleV2(d *schema.ResourceData, raw interface{}) secgroups.C CIDR: rawMap["cidr"].(string), FromGroupID: rawMap["from_group_id"].(string), } - - return createRuleOpts } From b9395b36d28b7267d3968f43495f48ca75a4757f Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:22:37 -0700 Subject: [PATCH 199/295] update client methods --- .../resource_openstack_compute_instance_v2_test.go | 12 +++--------- .../resource_openstack_compute_keypair_v2_test.go | 12 +++--------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go index ff2457d09..77559fa3a 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go @@ -7,8 +7,6 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" ) @@ -33,9 +31,7 @@ func TestAccComputeV2Instance_basic(t *testing.T) { func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: OS_REGION_NAME, - }) + computeClient, err := config.computeV2Client(OS_REGION_NAME) if err != nil { return fmt.Errorf("(testAccCheckComputeV2InstanceDestroy) Error creating OpenStack compute client: %s", err) } @@ -65,10 +61,8 @@ func testAccCheckComputeV2InstanceExists(t *testing.T, n string, instance *serve return fmt.Errorf("No ID is set") } - osClient := testAccProvider.Meta().(*Config).osClient - computeClient, err := openstack.NewComputeV2(osClient, gophercloud.EndpointOpts{ - Region: OS_REGION_NAME, - }) + config := testAccProvider.Meta().(*Config) + computeClient, err := config.computeV2Client(OS_REGION_NAME) if err != nil { return fmt.Errorf("(testAccCheckComputeV2InstanceExists) Error creating OpenStack compute client: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go index 0456341df..da090bcd8 100644 --- a/builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_keypair_v2_test.go @@ -7,8 +7,6 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/rackspace/gophercloud" - "github.com/rackspace/gophercloud/openstack" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" ) @@ -32,9 +30,7 @@ func TestAccComputeV2Keypair_basic(t *testing.T) { func testAccCheckComputeV2KeypairDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) - computeClient, err := openstack.NewComputeV2(config.osClient, gophercloud.EndpointOpts{ - Region: OS_REGION_NAME, - }) + computeClient, err := config.computeV2Client(OS_REGION_NAME) if err != nil { return fmt.Errorf("(testAccCheckComputeV2KeypairDestroy) Error creating OpenStack compute client: %s", err) } @@ -64,10 +60,8 @@ func testAccCheckComputeV2KeypairExists(t *testing.T, n string, kp *keypairs.Key return fmt.Errorf("No ID is set") } - osClient := testAccProvider.Meta().(*Config).osClient - computeClient, err := openstack.NewComputeV2(osClient, gophercloud.EndpointOpts{ - Region: OS_REGION_NAME, - }) + config := testAccProvider.Meta().(*Config) + computeClient, err := config.computeV2Client(OS_REGION_NAME) if err != nil { return fmt.Errorf("(testAccCheckComputeV2KeypairExists) Error creating OpenStack compute client: %s", err) } From 7cdb790ece6009594beccec787f26b94108b1bb2 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:35:42 -0700 Subject: [PATCH 200/295] update compute intance docs: 'networks -> 'network' --- .../providers/openstack/r/compute_instance_v2.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown index 4429174fb..6a9a5ac95 100644 --- a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown @@ -48,7 +48,7 @@ The following arguments are supported: * `availability_zone` - (Optional) The availability zone in which to create the server. Changing this creates a new server. -* `networks` - (Optional) An array of one or more networks to attach to the +* `network` - (Optional) An array of one or more networks to attach to the instance. The network object structure is documented below. Changing this creates a new server. From 3427597bd0d9204201a4d7cb25ba8b79a3ae9384 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:37:11 -0700 Subject: [PATCH 201/295] update docs to remove security group rule and lb member files --- .../r/compute_secgroup_v2.html.markdown | 30 ++++++++ .../r/compute_secgrouprule_v2.html.markdown | 69 ------------------- .../openstack/r/lb_member_v1.html.markdown | 58 ---------------- .../openstack/r/lb_pool_v1.html.markdown | 28 +++++++- website/source/layouts/openstack.erb | 6 -- 5 files changed, 57 insertions(+), 134 deletions(-) delete mode 100644 website/source/docs/providers/openstack/r/compute_secgrouprule_v2.html.markdown delete mode 100644 website/source/docs/providers/openstack/r/lb_member_v1.html.markdown diff --git a/website/source/docs/providers/openstack/r/compute_secgroup_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_secgroup_v2.html.markdown index 86f546ed2..50f328502 100644 --- a/website/source/docs/providers/openstack/r/compute_secgroup_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_secgroup_v2.html.markdown @@ -16,6 +16,12 @@ Manages a V2 security group resource within OpenStack. resource "openstack_compute_secgroup_v2" "secgroup_1" { name = "my_secgroup" description = "my security group" + rule { + from_port = 22 + to_port = 22 + ip_protocol = "tcp" + cidr = "0.0.0.0/0" + } } ``` @@ -34,6 +40,29 @@ The following arguments are supported: * `description` - (Required) A description for the security group. Changing this updates the `description` of an existing security group. +* `rule` - (Optional) A rule describing how the security group operates. The + rule object structure is documented below. Changing this updates the + security group rules. + +The `rule` block supports: + +* `from_port` - (Required) An integer representing the lower bound of the port +range to open. Changing this creates a new security group rule. + +* `to_port` - (Required) An integer representing the upper bound of the port +range to open. Changing this creates a new security group rule. + +* `ip_protocol` - (Required) The protocol type that will be allowed. Changing +this creates a new security group rule. + +* `cidr` - (Optional) Required if `from_group_id` is empty. The IP range that +will be the source of network traffic to the security group. Use 0.0.0.0./0 +to allow all IP addresses. Changing this creates a new security group rule. + +* `from_group_id - (Optional) Required if `cidr` is empty. The ID of a group +from which to forward traffic to the parent group. Changing +this creates a new security group rule. + ## Attributes Reference The following attributes are exported: @@ -41,3 +70,4 @@ The following attributes are exported: * `region` - See Argument Reference above. * `name` - See Argument Reference above. * `description` - See Argument Reference above. +* `rule` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/compute_secgrouprule_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_secgrouprule_v2.html.markdown deleted file mode 100644 index 06385a6c8..000000000 --- a/website/source/docs/providers/openstack/r/compute_secgrouprule_v2.html.markdown +++ /dev/null @@ -1,69 +0,0 @@ ---- -layout: "openstack" -page_title: "OpenStack: openstack_compute_secgrouprule_v2" -sidebar_current: "docs-openstack-resource-compute-secgrouprule-v2" -description: |- - Manages a V2 security group rule resource within OpenStack. ---- - -# openstack\_compute\_secgrouprule_v2 - -Manages a V2 security group rule resource within OpenStack. - -## Example Usage - -``` -resource "openstack_compute_secgroup_v2" "secgroup_1" { - name = "my_secgroup" - description = "my security group" -} - -resource "openstack_compute_secgrouprule_v2" "secgrouprule_1" { - group_id = "${openstack_compute_secgroup_v2.secgroup_1.id}" - from_port = 22 - to_port = 22 - ip_protocol = "TCP" - cidr = "0.0.0.0/0" -} -``` - -## Argument Reference - -The following arguments are supported: - -* `region` - (Required) The region in which to obtain the V2 Compute client. - A Compute client is needed to create a security group rule. If omitted, the - `OS_REGION_NAME` environment variable is used. Changing this creates a new - security group rule. - -* `group_id` - (Required) The ID of the group to which this rule will be added. - Changing this creates a new security group rule. - -* `from_port` - (Required) An integer representing the lower bound of the port - range to open. Changing this creates a new security group rule. - -* `to_port` - (Required) An integer representing the upper bound of the port - range to open. Changing this creates a new security group rule. - -* `ip_protocol` - (Required) The protocol type that will be allowed. Changing - this creates a new security group rule. - -* `cidr` - (Optional) Required if `from_group_id` is empty. The IP range that - will be the source of network traffic to the security group. Use 0.0.0.0./0 - to allow all IP addresses. Changing this creates a new security group rule. - -* `from_group_id - (Optional) Required if `cidr` is empty. The ID of a group - from which to forward traffic to the parent group. Changing - this creates a new security group rule. - -## Attributes Reference - -The following attributes are exported: - -* `region` - See Argument Reference above. -* `group_id` - See Argument Reference above. -* `from_port` - See Argument Reference above. -* `to_port` - See Argument Reference above. -* `ip_protocol` - See Argument Reference above. -* `cidr` - See Argument Reference above. -* `from_group_id` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/lb_member_v1.html.markdown b/website/source/docs/providers/openstack/r/lb_member_v1.html.markdown deleted file mode 100644 index 2d1be74dc..000000000 --- a/website/source/docs/providers/openstack/r/lb_member_v1.html.markdown +++ /dev/null @@ -1,58 +0,0 @@ ---- -layout: "openstack" -page_title: "OpenStack: openstack_lb_member_v1" -sidebar_current: "docs-openstack-resource-lb-member-v1" -description: |- - Manages a V1 load balancer member resource within OpenStack. ---- - -# openstack\_lb\_member_v1 - -Manages a V1 load balancer member resource within OpenStack. - -## Example Usage - -``` -resource "openstack_lb_member_v1" "node_1" { - address = "196.172.0.1" - port = 80 - pool_id = "12345" - admin_state_up = true -} -``` - -## Argument Reference - -The following arguments are supported: - -* `region` - (Required) The region in which to obtain the V2 Networking client. - A Networking client is needed to create an LB member. If omitted, the - `OS_REGION_NAME` environment variable is used. Changing this creates a new - LB member. - -* `address` - (Required) The IP address of the member. Changing this creates a - new member. - -* `port` - (Required) An integer representing the port on which the member is - hosted. Changing this creates a new member. - -* `pool_id` - (Required) The pool to which this member will belong. Changing - this creates a new member. - -* `admin_state_up` - (Optional) The administrative state of the member. - Acceptable values are 'true' and 'false'. Changing this value updates the - state of the existing member. - -* `tenant_id` - (Optional) The owner of the member. Required if admin wants to - create a pool member for another tenant. Changing this creates a new member. - -## Attributes Reference - -The following attributes are exported: - -* `region` - See Argument Reference above. -* `address` - See Argument Reference above. -* `port` - See Argument Reference above. -* `pool_id` - See Argument Reference above. -* `admin_state_up` - See Argument Reference above. -* `tenant_id` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/lb_pool_v1.html.markdown b/website/source/docs/providers/openstack/r/lb_pool_v1.html.markdown index f0de6917a..5ddbdf1af 100644 --- a/website/source/docs/providers/openstack/r/lb_pool_v1.html.markdown +++ b/website/source/docs/providers/openstack/r/lb_pool_v1.html.markdown @@ -18,7 +18,12 @@ resource "openstack_lb_pool_v1" "pool_1" { protocol = "HTTP" subnet_id = "12345" lb_method = "ROUND_ROBIN" - monitor_id = "67890" + monitor_ids = ["67890"] + member { + address = "192.168.0.1" + port = 80 + admin_state_up = "true" + } } ``` @@ -51,6 +56,26 @@ The following arguments are supported: * `monitor_ids` - (Optional) A list of IDs of monitors to associate with the pool. +* `member` - (Optional) An existing node to add to the pool. Changing this + updates the members of the pool. The member object structure is documented + below. + +The `member` block supports: + +* `address` - (Required) The IP address of the member. Changing this creates a +new member. + +* `port` - (Required) An integer representing the port on which the member is +hosted. Changing this creates a new member. + +* `admin_state_up` - (Optional) The administrative state of the member. +Acceptable values are 'true' and 'false'. Changing this value updates the +state of the existing member. + +* `tenant_id` - (Optional) The owner of the member. Required if admin wants to +create a pool member for another tenant. Changing this creates a new member. + + ## Attributes Reference The following attributes are exported: @@ -62,3 +87,4 @@ The following attributes are exported: * `lb_method` - See Argument Reference above. * `tenant_id` - See Argument Reference above. * `monitor_id` - See Argument Reference above. +* `member` - See Argument Reference above. diff --git a/website/source/layouts/openstack.erb b/website/source/layouts/openstack.erb index 37b0c2a7c..71cc9eb9e 100644 --- a/website/source/layouts/openstack.erb +++ b/website/source/layouts/openstack.erb @@ -22,12 +22,6 @@ > openstack_compute_secgroup_v2 - > - openstack_compute_secgrouprule_v2 - - > - openstack_lb_member_v1 - > openstack_lb_monitor_v1 From fa15d41d45cd9a10615194c498b899a1b36ccf85 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:55:42 -0700 Subject: [PATCH 202/295] UpdateOpts not optional --- .../resource_openstack_lb_monitor_v1.go | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go index ca5ad7659..7483b17e4 100644 --- a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go @@ -171,25 +171,15 @@ func resourceLBMonitorV1Update(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } - var updateOpts monitors.UpdateOpts - if d.HasChange("delay") { - updateOpts.Delay = d.Get("delay").(int) - } - if d.HasChange("timeout") { - updateOpts.Timeout = d.Get("timeout").(int) - } - if d.HasChange("max_retries") { - updateOpts.MaxRetries = d.Get("max_retries").(int) - } - if d.HasChange("url_path") { - updateOpts.URLPath = d.Get("url_path").(string) - } - if d.HasChange("http_method") { - updateOpts.HTTPMethod = d.Get("http_method").(string) - } - if d.HasChange("expected_codes") { - updateOpts.ExpectedCodes = d.Get("expected_codes").(string) + updateOpts := monitors.UpdateOpts{ + Delay: d.Get("delay").(int), + Timeout: d.Get("timeout").(int), + MaxRetries: d.Get("max_retries").(int), + URLPath: d.Get("url_path").(string), + HTTPMethod: d.Get("http_method").(string), + ExpectedCodes: d.Get("expected_codes").(string), } + if d.HasChange("admin_state_up") { asuRaw := d.Get("admin_state_up").(string) if asuRaw != "" { From 66129632b358a44949443590b89d5aed1a72e061 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:56:11 -0700 Subject: [PATCH 203/295] security groups acceptance tests --- ...urce_openstack_compute_secgroup_v2_test.go | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_compute_secgroup_v2_test.go diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2_test.go new file mode 100644 index 000000000..e78865b8a --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2_test.go @@ -0,0 +1,90 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" +) + +func TestAccComputeV2SecGroup_basic(t *testing.T) { + var secgroup secgroups.SecurityGroup + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeV2SecGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeV2SecGroup_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeV2SecGroupExists(t, "openstack_compute_secgroup_v2.foo", &secgroup), + ), + }, + }, + }) +} + +func testAccCheckComputeV2SecGroupDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + computeClient, err := config.computeV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckComputeV2SecGroupDestroy) Error creating OpenStack compute client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_compute_secgroup_v2" { + continue + } + + _, err := secgroups.Get(computeClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Security group still exists") + } + } + + return nil +} + +func testAccCheckComputeV2SecGroupExists(t *testing.T, n string, secgroup *secgroups.SecurityGroup) 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") + } + + config := testAccProvider.Meta().(*Config) + computeClient, err := config.computeV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckComputeV2SecGroupExists) Error creating OpenStack compute client: %s", err) + } + + found, err := secgroups.Get(computeClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Security group not found") + } + + *secgroup = *found + + return nil + } +} + +var testAccComputeV2SecGroup_basic = fmt.Sprintf(` + resource "openstack_compute_secgroup_v2" "foo" { + region = "%s" + name = "test_group_1" + description = "first test security group" + }`, + OS_REGION_NAME) From e08e97304f91d9d674e874ac4acb505bb0be6d73 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:56:27 -0700 Subject: [PATCH 204/295] lb monitors acceptance tests --- .../resource_openstack_lb_monitor_v1_test.go | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_lb_monitor_v1_test.go diff --git a/builtin/providers/openstack/resource_openstack_lb_monitor_v1_test.go b/builtin/providers/openstack/resource_openstack_lb_monitor_v1_test.go new file mode 100644 index 000000000..5aaf61d2c --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_monitor_v1_test.go @@ -0,0 +1,110 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/monitors" +) + +func TestAccLBV1Monitor_basic(t *testing.T) { + var monitor monitors.Monitor + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLBV1MonitorDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccLBV1Monitor_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLBV1MonitorExists(t, "openstack_lb_monitor_v1.monitor_1", &monitor), + ), + }, + resource.TestStep{ + Config: testAccLBV1Monitor_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_lb_monitor_v1.monitor_1", "delay", "20"), + ), + }, + }, + }) +} + +func testAccCheckLBV1MonitorDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckLBV1MonitorDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_lb_monitor_v1" { + continue + } + + _, err := monitors.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("LB monitor still exists") + } + } + + return nil +} + +func testAccCheckLBV1MonitorExists(t *testing.T, n string, monitor *monitors.Monitor) 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") + } + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckLBV1MonitorExists) Error creating OpenStack networking client: %s", err) + } + + found, err := monitors.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Monitor not found") + } + + *monitor = *found + + return nil + } +} + +var testAccLBV1Monitor_basic = fmt.Sprintf(` + resource "openstack_lb_monitor_v1" "monitor_1" { + region = "%s" + type = "PING" + delay = 30 + timeout = 5 + max_retries = 3 + admin_state_up = "true" + }`, + OS_REGION_NAME) + +var testAccLBV1Monitor_update = fmt.Sprintf(` + resource "openstack_lb_monitor_v1" "monitor_1" { + region = "%s" + type = "PING" + delay = 20 + timeout = 5 + max_retries = 3 + admin_state_up = "true" + }`, + OS_REGION_NAME) From e7a69d0a6c59450459bfe9aedc1065591ad24771 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:56:40 -0700 Subject: [PATCH 205/295] lb pools acceptance tests --- .../resource_openstack_lb_pool_v1_test.go | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_lb_pool_v1_test.go diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v1_test.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1_test.go new file mode 100644 index 000000000..1889c2384 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1_test.go @@ -0,0 +1,134 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/pools" +) + +func TestAccLBV1Pool_basic(t *testing.T) { + var pool pools.Pool + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLBV1PoolDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccLBV1Pool_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLBV1PoolExists(t, "openstack_lb_pool_v1.pool_1", &pool), + ), + }, + resource.TestStep{ + Config: testAccLBV1Pool_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_lb_pool_v1.pool_1", "name", "tf_test_lb_pool_updated"), + ), + }, + }, + }) +} + +func testAccCheckLBV1PoolDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckLBV1PoolDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_lb_pool_v1" { + continue + } + + _, err := pools.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("LB Pool still exists") + } + } + + return nil +} + +func testAccCheckLBV1PoolExists(t *testing.T, n string, pool *pools.Pool) 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") + } + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckLBV1PoolExists) Error creating OpenStack networking client: %s", err) + } + + found, err := pools.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Pool not found") + } + + *pool = *found + + return nil + } +} + +var testAccLBV1Pool_basic = fmt.Sprintf(` + resource "openstack_networking_network_v2" "network_1" { + region = "%s" + name = "network_1" + admin_state_up = "true" + } + + resource "openstack_networking_subnet_v2" "subnet_1" { + region = "%s" + network_id = "${openstack_networking_network_v2.network_1.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + } + + resource "openstack_lb_pool_v1" "pool_1" { + region = "%s" + name = "tf_test_lb_pool" + protocol = "HTTP" + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + lb_method = "ROUND_ROBIN" + }`, + OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME) + +var testAccLBV1Pool_update = fmt.Sprintf(` + resource "openstack_networking_network_v2" "network_1" { + region = "%s" + name = "network_1" + admin_state_up = "true" + } + + resource "openstack_networking_subnet_v2" "subnet_1" { + region = "%s" + network_id = "${openstack_networking_network_v2.network_1.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + } + + resource "openstack_lb_pool_v1" "pool_1" { + region = "%s" + name = "tf_test_lb_pool_updated" + protocol = "HTTP" + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + lb_method = "ROUND_ROBIN" + }`, + OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME) From d46d9a6540fd2375db3c494d5f70ac7eaa8f0d84 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:56:54 -0700 Subject: [PATCH 206/295] lb vips acceptance tests --- .../resource_openstack_lb_vip_v1_test.go | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_lb_vip_v1_test.go diff --git a/builtin/providers/openstack/resource_openstack_lb_vip_v1_test.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1_test.go new file mode 100644 index 000000000..f30cd9d56 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1_test.go @@ -0,0 +1,152 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/lbaas/vips" +) + +func TestAccLBV1VIP_basic(t *testing.T) { + var vip vips.VirtualIP + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLBV1VIPDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccLBV1VIP_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLBV1VIPExists(t, "openstack_lb_vip_v1.vip_1", &vip), + ), + }, + resource.TestStep{ + Config: testAccLBV1VIP_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_lb_vip_v1.vip_1", "name", "tf_test_lb_vip_updated"), + ), + }, + }, + }) +} + +func testAccCheckLBV1VIPDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckLBV1VIPDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_lb_vip_v1" { + continue + } + + _, err := vips.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("LB VIP still exists") + } + } + + return nil +} + +func testAccCheckLBV1VIPExists(t *testing.T, n string, vip *vips.VirtualIP) 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") + } + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckLBV1VIPExists) Error creating OpenStack networking client: %s", err) + } + + found, err := vips.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("VIP not found") + } + + *vip = *found + + return nil + } +} + +var testAccLBV1VIP_basic = fmt.Sprintf(` + resource "openstack_networking_network_v2" "network_1" { + region = "%s" + name = "network_1" + admin_state_up = "true" + } + + resource "openstack_networking_subnet_v2" "subnet_1" { + region = "%s" + network_id = "${openstack_networking_network_v2.network_1.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + } + + resource "openstack_lb_pool_v1" "pool_1" { + region = "%s" + name = "tf_test_lb_pool" + protocol = "HTTP" + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + lb_method = "ROUND_ROBIN" + } + + resource "openstack_lb_vip_v1" "vip_1" { + region = "RegionOne" + name = "tf_test_lb_vip" + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + protocol = "HTTP" + port = 80 + pool_id = "${openstack_lb_pool_v1.pool_1.id}" + }`, + OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME) + +var testAccLBV1VIP_update = fmt.Sprintf(` + resource "openstack_networking_network_v2" "network_1" { + region = "%s" + name = "network_1" + admin_state_up = "true" + } + + resource "openstack_networking_subnet_v2" "subnet_1" { + region = "%s" + network_id = "${openstack_networking_network_v2.network_1.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + } + + resource "openstack_lb_pool_v1" "pool_1" { + region = "%s" + name = "tf_test_lb_pool" + protocol = "HTTP" + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + lb_method = "ROUND_ROBIN" + } + + resource "openstack_lb_vip_v1" "vip_1" { + region = "RegionOne" + name = "tf_test_lb_vip_updated" + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + protocol = "HTTP" + port = 80 + pool_id = "${openstack_lb_pool_v1.pool_1.id}" + }`, + OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME) From e0409340776a65abc7ada949f1c92bb34e8cabb1 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:57:10 -0700 Subject: [PATCH 207/295] networking networks acceptance tests --- ...ce_openstack_networking_network_v2_test.go | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_networking_network_v2_test.go diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go new file mode 100644 index 000000000..5bff60532 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2_test.go @@ -0,0 +1,104 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/networking/v2/networks" +) + +func TestAccNetworkingV2Network_basic(t *testing.T) { + var network networks.Network + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2NetworkDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkingV2Network_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2NetworkExists(t, "openstack_networking_network_v2.foo", &network), + ), + }, + resource.TestStep{ + Config: testAccNetworkingV2Network_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_networking_network_v2.foo", "name", "network_2"), + ), + }, + }, + }) +} + +func testAccCheckNetworkingV2NetworkDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2NetworkDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_networking_network_v2" { + continue + } + + _, err := networks.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Network still exists") + } + } + + return nil +} + +func testAccCheckNetworkingV2NetworkExists(t *testing.T, n string, network *networks.Network) 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") + } + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2NetworkExists) Error creating OpenStack networking client: %s", err) + } + + found, err := networks.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Network not found") + } + + *network = *found + + return nil + } +} + +var testAccNetworkingV2Network_basic = fmt.Sprintf(` + resource "openstack_networking_network_v2" "foo" { + region = "%s" + name = "network_1" + admin_state_up = "true" + }`, + OS_REGION_NAME) + +var testAccNetworkingV2Network_update = fmt.Sprintf(` + resource "openstack_networking_network_v2" "foo" { + region = "%s" + name = "network_2" + admin_state_up = "true" + }`, + OS_REGION_NAME) From 08672e697ebe92849f387241c6d36f202da8d63c Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 17:57:25 -0700 Subject: [PATCH 208/295] networking subnets acceptance tests --- ...rce_openstack_networking_subnet_v2_test.go | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_networking_subnet_v2_test.go diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_subnet_v2_test.go new file mode 100644 index 000000000..d7f6116e9 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_subnet_v2_test.go @@ -0,0 +1,119 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/networking/v2/subnets" +) + +func TestAccNetworkingV2Subnet_basic(t *testing.T) { + var subnet subnets.Subnet + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2SubnetDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkingV2Subnet_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2SubnetExists(t, "openstack_networking_subnet_v2.subnet_1", &subnet), + ), + }, + resource.TestStep{ + Config: testAccNetworkingV2Subnet_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_networking_subnet_v2.subnet_1", "name", "tf-test-subnet"), + resource.TestCheckResourceAttr("openstack_networking_subnet_v2.subnet_1", "gateway_ip", "192.68.0.1"), + ), + }, + }, + }) +} + +func testAccCheckNetworkingV2SubnetDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2SubnetDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_networking_subnet_v2" { + continue + } + + _, err := subnets.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Subnet still exists") + } + } + + return nil +} + +func testAccCheckNetworkingV2SubnetExists(t *testing.T, n string, subnet *subnets.Subnet) 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") + } + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2SubnetExists) Error creating OpenStack networking client: %s", err) + } + + found, err := subnets.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Subnet not found") + } + + *subnet = *found + + return nil + } +} + +var testAccNetworkingV2Subnet_basic = fmt.Sprintf(` + resource "openstack_networking_network_v2" "network_1" { + region = "%s" + name = "network_1" + admin_state_up = "true" + } + + resource "openstack_networking_subnet_v2" "subnet_1" { + region = "%s" + network_id = "${openstack_networking_network_v2.network_1.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + }`, OS_REGION_NAME, OS_REGION_NAME) + +var testAccNetworkingV2Subnet_update = fmt.Sprintf(` + resource "openstack_networking_network_v2" "network_1" { + region = "%s" + name = "network_1" + admin_state_up = "true" + } + + resource "openstack_networking_subnet_v2" "subnet_1" { + region = "%s" + name = "tf-test-subnet" + network_id = "${openstack_networking_network_v2.network_1.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + gateway_ip = "192.68.0.1" + }`, OS_REGION_NAME, OS_REGION_NAME) From 43564d1c5cf3f7533615699574bbceec9f1390c0 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sat, 31 Jan 2015 20:51:50 -0700 Subject: [PATCH 209/295] object storage container v1 ops --- builtin/providers/openstack/config.go | 6 + builtin/providers/openstack/provider.go | 17 +- ...ce_openstack_objectstorage_container_v1.go | 148 ++++++++++++++++++ 3 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go diff --git a/builtin/providers/openstack/config.go b/builtin/providers/openstack/config.go index 2e37c4fd0..864dc9ab7 100644 --- a/builtin/providers/openstack/config.go +++ b/builtin/providers/openstack/config.go @@ -53,3 +53,9 @@ func (c *Config) networkingV2Client(region string) (*gophercloud.ServiceClient, Region: region, }) } + +func (c *Config) objectStorageV1Client(region string) (*gophercloud.ServiceClient, error) { + return openstack.NewObjectStorageV1(c.osClient, gophercloud.EndpointOpts{ + Region: region, + }) +} diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index a53882f84..019c7b386 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -59,14 +59,15 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "openstack_compute_instance_v2": resourceComputeInstanceV2(), - "openstack_compute_keypair_v2": resourceComputeKeypairV2(), - "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), - "openstack_lb_monitor_v1": resourceLBMonitorV1(), - "openstack_lb_pool_v1": resourceLBPoolV1(), - "openstack_lb_vip_v1": resourceLBVipV1(), - "openstack_networking_network_v2": resourceNetworkingNetworkV2(), - "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), + "openstack_compute_instance_v2": resourceComputeInstanceV2(), + "openstack_compute_keypair_v2": resourceComputeKeypairV2(), + "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), + "openstack_lb_monitor_v1": resourceLBMonitorV1(), + "openstack_lb_pool_v1": resourceLBPoolV1(), + "openstack_lb_vip_v1": resourceLBVipV1(), + "openstack_networking_network_v2": resourceNetworkingNetworkV2(), + "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), + "openstack_objectstorage_container_v1": resourceObjectStorageContainerV1(), }, ConfigureFunc: configureProvider, diff --git a/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go b/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go new file mode 100644 index 000000000..f9b5d5250 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go @@ -0,0 +1,148 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers" +) + +func resourceObjectStorageContainerV1() *schema.Resource { + return &schema.Resource{ + Create: resourceObjectStorageContainerV1Create, + Read: resourceObjectStorageContainerV1Read, + Update: resourceObjectStorageContainerV1Update, + Delete: resourceObjectStorageContainerV1Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + "container_read": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "container_sync_to": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "container_sync_key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "container_write": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "content_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "metadata": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + ForceNew: false, + }, + }, + } +} + +func resourceObjectStorageContainerV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + objectStorgeClient, err := config.objectStorageV1Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack object storage client: %s", err) + } + + cn := d.Get("name").(string) + + createOpts := &containers.CreateOpts{ + ContainerRead: d.Get("container_read").(string), + ContainerSyncTo: d.Get("container_sync_to").(string), + ContainerSyncKey: d.Get("container_sync_key").(string), + ContainerWrite: d.Get("container_write").(string), + ContentType: d.Get("content_type").(string), + Metadata: resourceContainerMetadataV2(d), + } + + log.Printf("[INFO] Requesting container creation") + _, err = containers.Create(objectStorgeClient, cn, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack container: %s", err) + } + log.Printf("[INFO] Container ID: %s", cn) + + // Store the ID now + d.SetId(cn) + + return resourceObjectStorageContainerV1Read(d, meta) +} + +func resourceObjectStorageContainerV1Read(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceObjectStorageContainerV1Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + objectStorgeClient, err := config.objectStorageV1Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack object storage client: %s", err) + } + + updateOpts := containers.UpdateOpts{ + ContainerRead: d.Get("container_read").(string), + ContainerSyncTo: d.Get("container_sync_to").(string), + ContainerSyncKey: d.Get("container_sync_key").(string), + ContainerWrite: d.Get("container_write").(string), + ContentType: d.Get("content_type").(string), + } + + _, err = containers.Update(objectStorgeClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack container: %s", err) + } + + if d.HasChange("metadata") { + updateOpts.Metadata = resourceContainerMetadataV2(d) + } + + return resourceObjectStorageContainerV1Read(d, meta) +} + +func resourceObjectStorageContainerV1Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + objectStorgeClient, err := config.objectStorageV1Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack object storage client: %s", err) + } + + _, err = containers.Delete(objectStorgeClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error deleting OpenStack container: %s", err) + } + + d.SetId("") + return nil +} + +func resourceContainerMetadataV2(d *schema.ResourceData) map[string]string { + m := make(map[string]string) + for key, val := range d.Get("metadata").(map[string]interface{}) { + m[key] = val.(string) + } + return m +} From a5147f472bfcd3b5c02785ebd69e4ed5a788899b Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 1 Feb 2015 00:14:43 -0700 Subject: [PATCH 210/295] update metadata before actual Update op --- .../resource_openstack_objectstorage_container_v1.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go b/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go index f9b5d5250..687e65b57 100644 --- a/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go +++ b/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go @@ -111,15 +111,15 @@ func resourceObjectStorageContainerV1Update(d *schema.ResourceData, meta interfa ContentType: d.Get("content_type").(string), } + if d.HasChange("metadata") { + updateOpts.Metadata = resourceContainerMetadataV2(d) + } + _, err = containers.Update(objectStorgeClient, d.Id(), updateOpts).Extract() if err != nil { return fmt.Errorf("Error updating OpenStack container: %s", err) } - if d.HasChange("metadata") { - updateOpts.Metadata = resourceContainerMetadataV2(d) - } - return resourceObjectStorageContainerV1Read(d, meta) } From f1ac6dbfec477132673a683c4cdd0ea9ede244ce Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 1 Feb 2015 00:15:19 -0700 Subject: [PATCH 211/295] block storage volume v1 ops --- ...source_openstack_blockstorage_volume_v1.go | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go diff --git a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go new file mode 100644 index 000000000..ee86b51d4 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go @@ -0,0 +1,226 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" +) + +func resourceBlockStorageVolumeV1() *schema.Resource { + return &schema.Resource{ + Create: resourceBlockStorageVolumeV1Create, + Read: resourceBlockStorageVolumeV1Read, + Update: resourceBlockStorageVolumeV1Update, + Delete: resourceBlockStorageVolumeV1Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, + "size": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "metadata": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + ForceNew: false, + }, + "snapshot_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "source_vol_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "image_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "volume_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceBlockStorageVolumeV1Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + createOpts := &volumes.CreateOpts{ + Description: d.Get("description").(string), + Name: d.Get("name").(string), + Size: d.Get("size").(int), + SnapshotID: d.Get("snapshot_id").(string), + SourceVolID: d.Get("source_vol_id").(string), + ImageID: d.Get("image_id").(string), + VolumeType: d.Get("volume_type").(string), + Metadata: resourceContainerMetadataV2(d), + } + + log.Printf("[INFO] Requesting volume creation") + v, err := volumes.Create(blockStorageClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack volume: %s", err) + } + log.Printf("[INFO] Volume ID: %s", v.ID) + + // Store the ID now + d.SetId(v.ID) + + // Wait for the volume to become running. + log.Printf( + "[DEBUG] Waiting for volume (%s) to become running", + v.ID) + + stateConf := &resource.StateChangeConf{ + Target: "available", + Refresh: VolumeV1StateRefreshFunc(blockStorageClient, v.ID), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for volume (%s) to become ready: %s", + v.ID, err) + } + + return resourceBlockStorageVolumeV1Read(d, meta) +} + +func resourceBlockStorageVolumeV1Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + v, err := volumes.Get(blockStorageClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error retrieving OpenStack volume: %s", err) + } + + log.Printf("[DEBUG] Retreived volume %s: %+v", d.Id(), v) + + d.Set("region", d.Get("region").(string)) + d.Set("description", v.Description) + d.Set("name", v.Name) + d.Set("size", v.Size) + d.Set("snapshot_id", v.SnapshotID) + d.Set("source_vol_id", v.SourceVolID) + d.Set("volume_type", v.VolumeType) + d.Set("metadata", v.Metadata) + + return nil +} + +func resourceBlockStorageVolumeV1Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + updateOpts := volumes.UpdateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + } + + if d.HasChange("metadata") { + updateOpts.Metadata = resourceVolumeMetadataV1(d) + } + + _, err = volumes.Update(blockStorageClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack volume: %s", err) + } + + return resourceBlockStorageVolumeV1Read(d, meta) +} + +func resourceBlockStorageVolumeV1Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + err = volumes.Delete(blockStorageClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack volume: %s", err) + } + + // Wait for the volume to delete before moving on. + log.Printf("[DEBUG] Waiting for volume (%s) to delete", d.Id()) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"deleting"}, + Target: "", + Refresh: VolumeV1StateRefreshFunc(blockStorageClient, d.Id()), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for volume (%s) to delete: %s", + d.Id(), err) + } + + d.SetId("") + return nil +} + +func resourceVolumeMetadataV1(d *schema.ResourceData) map[string]string { + m := make(map[string]string) + for key, val := range d.Get("metadata").(map[string]interface{}) { + m[key] = val.(string) + } + return m +} + +// VolumeV1StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// an OpenStack volume. +func VolumeV1StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + v, _ := volumes.Get(client, volumeID).Extract() + if v != nil { + return v, v.Status, nil + } + return nil, "", nil + } +} From acd5a033f0614cd2fd03b75c0bde32bfde46e8f0 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 1 Feb 2015 00:23:53 -0700 Subject: [PATCH 212/295] fix typo in client variable name --- .../resource_openstack_objectstorage_container_v1.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go b/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go index 687e65b57..692ed2e46 100644 --- a/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go +++ b/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go @@ -63,7 +63,7 @@ func resourceObjectStorageContainerV1() *schema.Resource { func resourceObjectStorageContainerV1Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - objectStorgeClient, err := config.objectStorageV1Client(d.Get("region").(string)) + objectStorageClient, err := config.objectStorageV1Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack object storage client: %s", err) } @@ -80,7 +80,7 @@ func resourceObjectStorageContainerV1Create(d *schema.ResourceData, meta interfa } log.Printf("[INFO] Requesting container creation") - _, err = containers.Create(objectStorgeClient, cn, createOpts).Extract() + _, err = containers.Create(objectStorageClient, cn, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack container: %s", err) } @@ -98,7 +98,7 @@ func resourceObjectStorageContainerV1Read(d *schema.ResourceData, meta interface func resourceObjectStorageContainerV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - objectStorgeClient, err := config.objectStorageV1Client(d.Get("region").(string)) + objectStorageClient, err := config.objectStorageV1Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack object storage client: %s", err) } @@ -115,7 +115,7 @@ func resourceObjectStorageContainerV1Update(d *schema.ResourceData, meta interfa updateOpts.Metadata = resourceContainerMetadataV2(d) } - _, err = containers.Update(objectStorgeClient, d.Id(), updateOpts).Extract() + _, err = containers.Update(objectStorageClient, d.Id(), updateOpts).Extract() if err != nil { return fmt.Errorf("Error updating OpenStack container: %s", err) } @@ -125,12 +125,12 @@ func resourceObjectStorageContainerV1Update(d *schema.ResourceData, meta interfa func resourceObjectStorageContainerV1Delete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - objectStorgeClient, err := config.objectStorageV1Client(d.Get("region").(string)) + objectStorageClient, err := config.objectStorageV1Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack object storage client: %s", err) } - _, err = containers.Delete(objectStorgeClient, d.Id()).Extract() + _, err = containers.Delete(objectStorageClient, d.Id()).Extract() if err != nil { return fmt.Errorf("Error deleting OpenStack container: %s", err) } From d2169e0e96c30210d5d2e8753c2927b5d31b2446 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 1 Feb 2015 00:33:53 -0700 Subject: [PATCH 213/295] block storage v1 acceptance tests --- ...e_openstack_blockstorage_volume_v1_test.go | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_blockstorage_volume_v1_test.go diff --git a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1_test.go b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1_test.go new file mode 100644 index 000000000..5404fd391 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1_test.go @@ -0,0 +1,138 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" +) + +func TestAccBlockStorageV1Volume_basic(t *testing.T) { + var volume volumes.Volume + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckBlockStorageV1VolumeDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccBlockStorageV1Volume_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckBlockStorageV1VolumeExists(t, "openstack_blockstorage_volume_v1.volume_1", &volume), + resource.TestCheckResourceAttr("openstack_blockstorage_volume_v1.volume_1", "name", "tf-test-volume"), + testAccCheckBlockStorageV1VolumeMetadata(&volume, "foo", "bar"), + ), + }, + resource.TestStep{ + Config: testAccBlockStorageV1Volume_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_blockstorage_volume_v1.volume_1", "name", "tf-test-volume-updated"), + testAccCheckBlockStorageV1VolumeMetadata(&volume, "foo", "bar"), + ), + }, + }, + }) +} + +func testAccCheckBlockStorageV1VolumeDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + blockStorageClient, err := config.blockStorageV1Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_blockstorage_volume_v1" { + continue + } + + _, err := volumes.Get(blockStorageClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Volume still exists") + } + } + + return nil +} + +func testAccCheckBlockStorageV1VolumeExists(t *testing.T, n string, volume *volumes.Volume) 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") + } + + config := testAccProvider.Meta().(*Config) + blockStorageClient, err := config.blockStorageV1Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } + + found, err := volumes.Get(blockStorageClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Volume not found") + } + + *volume = *found + + return nil + } +} + +func testAccCheckBlockStorageV1VolumeMetadata( + volume *volumes.Volume, k string, v string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if volume.Metadata == nil { + return fmt.Errorf("No metadata") + } + + for key, value := range volume.Metadata { + if k != key { + continue + } + + if v == value { + return nil + } + + return fmt.Errorf("Bad value for %s: %s", k, value) + } + + return fmt.Errorf("Metadata not found: %s", k) + } +} + +var testAccBlockStorageV1Volume_basic = fmt.Sprintf(` + resource "openstack_blockstorage_volume_v1" "volume_1" { + region = "%s" + name = "tf-test-volume" + description = "first test volume" + metadata{ + foo = "bar" + } + size = 1 + }`, + OS_REGION_NAME) + +var testAccBlockStorageV1Volume_update = fmt.Sprintf(` + resource "openstack_blockstorage_volume_v1" "volume_1" { + region = "%s" + name = "tf-test-volume-updated" + description = "first test volume" + metadata{ + foo = "bar" + } + size = 1 + }`, + OS_REGION_NAME) From a85067062d911f1370a54741a90515f9f3f3b7b5 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 1 Feb 2015 00:34:08 -0700 Subject: [PATCH 214/295] object storage v1 acceptance tests --- ...enstack_objectstorage_container_v1_test.go | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_objectstorage_container_v1_test.go diff --git a/builtin/providers/openstack/resource_openstack_objectstorage_container_v1_test.go b/builtin/providers/openstack/resource_openstack_objectstorage_container_v1_test.go new file mode 100644 index 000000000..9377ad2fb --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_objectstorage_container_v1_test.go @@ -0,0 +1,77 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/rackspace/gophercloud/openstack/objectstorage/v1/containers" +) + +func TestAccObjectStorageV1Container_basic(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckObjectStorageV1ContainerDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccObjectStorageV1Container_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_objectstorage_container_v1.container_1", "name", "tf-test-container"), + resource.TestCheckResourceAttr("openstack_objectstorage_container_v1.container_1", "content_type", "application/json"), + ), + }, + resource.TestStep{ + Config: testAccObjectStorageV1Container_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_objectstorage_container_v1.container_1", "content_type", "text/plain"), + ), + }, + }, + }) +} + +func testAccCheckObjectStorageV1ContainerDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + objectStorageClient, err := config.objectStorageV1Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating OpenStack object storage client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_objectstorage_container_v1" { + continue + } + + _, err := containers.Get(objectStorageClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Container still exists") + } + } + + return nil +} + +var testAccObjectStorageV1Container_basic = fmt.Sprintf(` + resource "openstack_objectstorage_container_v1" "container_1" { + region = "%s" + name = "tf-test-container" + metadata { + test = "true" + } + content_type = "application/json" + }`, + OS_REGION_NAME) + +var testAccObjectStorageV1Container_update = fmt.Sprintf(` + resource "openstack_objectstorage_container_v1" "container_1" { + region = "%s" + name = "tf-test-container" + metadata { + test = "true" + } + content_type = "text/plain" + }`, + OS_REGION_NAME) From 761d58df2f3cc4f2507b3950a53ec982d3884ac2 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 1 Feb 2015 00:34:37 -0700 Subject: [PATCH 215/295] add container and volume resources --- builtin/providers/openstack/config.go | 6 ++++++ builtin/providers/openstack/provider.go | 1 + 2 files changed, 7 insertions(+) diff --git a/builtin/providers/openstack/config.go b/builtin/providers/openstack/config.go index 864dc9ab7..d05662017 100644 --- a/builtin/providers/openstack/config.go +++ b/builtin/providers/openstack/config.go @@ -42,6 +42,12 @@ func (c *Config) loadAndValidate() error { return nil } +func (c *Config) blockStorageV1Client(region string) (*gophercloud.ServiceClient, error) { + return openstack.NewBlockStorageV1(c.osClient, gophercloud.EndpointOpts{ + Region: region, + }) +} + func (c *Config) computeV2Client(region string) (*gophercloud.ServiceClient, error) { return openstack.NewComputeV2(c.osClient, gophercloud.EndpointOpts{ Region: region, diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 019c7b386..180b5c3a7 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -59,6 +59,7 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ + "openstack_blockstorage_volume_v1": resourceBlockStorageVolumeV1(), "openstack_compute_instance_v2": resourceComputeInstanceV2(), "openstack_compute_keypair_v2": resourceComputeKeypairV2(), "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), From 9c128b7c9964727fe9d25aa3601fa930585d8c83 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 1 Feb 2015 01:11:01 -0700 Subject: [PATCH 216/295] docs for volume and container resources --- .../r/blockstorage_volume_v1.html.markdown | 68 +++++++++++++++++++ .../objectstorage_container_v1.html.markdown | 68 +++++++++++++++++++ website/source/layouts/openstack.erb | 6 ++ 3 files changed, 142 insertions(+) create mode 100644 website/source/docs/providers/openstack/r/blockstorage_volume_v1.html.markdown create mode 100644 website/source/docs/providers/openstack/r/objectstorage_container_v1.html.markdown diff --git a/website/source/docs/providers/openstack/r/blockstorage_volume_v1.html.markdown b/website/source/docs/providers/openstack/r/blockstorage_volume_v1.html.markdown new file mode 100644 index 000000000..0b91eb11c --- /dev/null +++ b/website/source/docs/providers/openstack/r/blockstorage_volume_v1.html.markdown @@ -0,0 +1,68 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_blockstorage_volume_v1" +sidebar_current: "docs-openstack-resource-blockstorage-volume-v1" +description: |- +Manages a V1 volume resource within OpenStack. +--- + +# openstack\_blockstorage\_volume_v1 + +Manages a V1 volume resource within OpenStack. + +## Example Usage + +``` +resource "openstack_blockstorage_volume_v1" "volume_1" { + region = "RegionOne" + name = "tf-test-volume" + description = "first test volume" + size = 3 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Required) The region in which to create the volume. If + omitted, the `OS_REGION_NAME` environment variable is used. Changing this + creates a new volume. + +* `size` - (Required) The size of the volume to create (in gigabytes). Changing + this creates a new volume. + +* `name` - (Optional) A unique name for the volume. Changing this updates the + volume's name. + +* `description` - (Optional) A description of the volume. Changing this updates + the volume's description. + +* `image_id` - (Optional) The image ID from which to create the volume. + Changing this creates a new volume. + +* `snapshot_id` - (Optional) The snapshot ID from which to create the volume. + Changing this creates a new volume. + +* `source_vol_id` - (Optional) The volume ID from which to create the volume. + Changing this creates a new volume. + +* `metadata` - (Optional) Metadata key/value pairs to associate with the volume. + Changing this updates the existing volume metadata. + +* `volume_type` - (Optional) The type of volume to create (either SATA or SSD). + Changing this creates a new volume. + +## Attributes Reference + +The following attributes are exported: + +* `region` - See Argument Reference above. +* `size` - See Argument Reference above. +* `name` - See Argument Reference above. +* `description` - See Argument Reference above. +* `image_id` - See Argument Reference above. +* `source_vol_id` - See Argument Reference above. +* `snapshot_id` - See Argument Reference above. +* `metadata` - See Argument Reference above. +* `volume_type` - See Argument Reference above. diff --git a/website/source/docs/providers/openstack/r/objectstorage_container_v1.html.markdown b/website/source/docs/providers/openstack/r/objectstorage_container_v1.html.markdown new file mode 100644 index 000000000..8101d1ca2 --- /dev/null +++ b/website/source/docs/providers/openstack/r/objectstorage_container_v1.html.markdown @@ -0,0 +1,68 @@ +--- +layout: "openstack" +page_title: "OpenStack: openstack_objectstorage_container_v1" +sidebar_current: "docs-openstack-resource-objectstorage-container-v1" +description: |- +Manages a V1 container resource within OpenStack. +--- + +# openstack\_objectstorage\_container_v1 + +Manages a V1 container resource within OpenStack. + +## Example Usage + +``` +resource "openstack_objectstorage_container_v1" "container_1" { + region = "RegionOne" + name = "tf-test-container-1" + metadata { + test = "true" + } + content_type = "application/json" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Required) The region in which to create the container. If + omitted, the `OS_REGION_NAME` environment variable is used. Changing this + creates a new container. + +* `name` - (Required) A unique name for the container. Changing this creates a + new container. + +* `container_read` - (Optional) Sets an access control list (ACL) that grants + read access. This header can contain a comma-delimited list of users that + can read the container (allows the GET method for all objects in the + container). Changing this updates the access control list read access. + +* `container_sync_to` - (Optional) The destination for container synchronization. + Changing this updates container synchronization. + +* `container_sync_key` - (Optional) The secret key for container synchronization. + Changing this updates container synchronization. + +* `container_write` - (Optional) Sets an ACL that grants write access. + Changing this updates the access control list write access. + +* `metadata` - (Optional) Custom key/value pairs to associate with the container. + Changing this updates the existing container metadata. + +* `content_type` - (Optional) The MIME type for the container. Changing this + updates the MIME type. + +## Attributes Reference + +The following attributes are exported: + +* `region` - See Argument Reference above. +* `name` - See Argument Reference above. +* `container_read` - See Argument Reference above. +* `container_sync_to` - See Argument Reference above. +* `container_sync_key` - See Argument Reference above. +* `container_write` - See Argument Reference above. +* `metadata` - See Argument Reference above. +* `content_type` - See Argument Reference above. diff --git a/website/source/layouts/openstack.erb b/website/source/layouts/openstack.erb index 71cc9eb9e..22afb4aeb 100644 --- a/website/source/layouts/openstack.erb +++ b/website/source/layouts/openstack.erb @@ -13,6 +13,9 @@ > Resources From 436ef9e53ba74b975ae8d29106c0efc35f17a3d6 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 1 Feb 2015 02:10:42 -0700 Subject: [PATCH 217/295] boot from volume ops and docs --- .../resource_openstack_compute_instance_v2.go | 41 +++++++++++++++++-- .../r/compute_instance_v2.html.markdown | 17 ++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 8059dec1b..766c5dea8 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" @@ -36,13 +37,13 @@ func resourceComputeInstanceV2() *schema.Resource { }, "image_ref": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: false, DefaultFunc: envDefaultFunc("OS_IMAGE_ID"), }, "flavor_ref": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: false, DefaultFunc: envDefaultFunc("OS_FLAVOR_ID"), }, @@ -113,6 +114,11 @@ func resourceComputeInstanceV2() *schema.Resource { Optional: true, ForceNew: true, }, + "block_device": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, }, } } @@ -145,6 +151,14 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e } } + if blockDeviceRaw, ok := d.Get("block_device").(map[string]interface{}); ok && blockDeviceRaw != nil { + blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw) + createOpts = &bootfromvolume.CreateOptsExt{ + createOpts, + blockDevice, + } + } + log.Printf("[INFO] Requesting instance creation") server, err := servers.Create(computeClient, createOpts).Extract() if err != nil { @@ -407,7 +421,7 @@ func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) e return nil } -// ServerStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// ServerV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch // an OpenStack instance. func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { @@ -450,3 +464,24 @@ func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string { } return m } + +func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interface{}) []bootfromvolume.BlockDevice { + sourceType := bootfromvolume.SourceType(bd["source_type"].(string)) + bfvOpts := []bootfromvolume.BlockDevice{ + bootfromvolume.BlockDevice{ + UUID: bd["uuid"].(string), + SourceType: sourceType, + }, + } + if vs, ok := bd["volume_size"].(int); ok { + bfvOpts[0].VolumeSize = vs + } + if dt, ok := bd["destination_type"].(string); ok { + bfvOpts[0].DestinationType = dt + } + if bi, ok := bd["boot_index"].(int); ok { + bfvOpts[0].BootIndex = bi + } + + return bfvOpts +} diff --git a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown index 6a9a5ac95..9095cb15a 100644 --- a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown @@ -62,6 +62,9 @@ The following arguments are supported: pair must already be created and associated with the tenant's account. Changing this creates a new server. +* `block_device` - (Optional) The object for booting by volume. The block_device + object structure is documented below. Changing this creates a new server. + The `network` block supports: * `uuid` - (Required unless `port` is provided) The network UUID to attach to @@ -73,6 +76,20 @@ The `network` block supports: * `fixed_ip` - (Optional) Specifies a fixed IP address to be used on this network. +The `block_device` block supports: + +* `uuid` - (Required) The UUID of the image, volume, or snapshot. + +* `source_type` - (Required) The source type of the device. Must be one of + "image", "volume", or "snapshot". + +* `volume_size` - (Optional) The size of the volume to create (in gigabytes). + +* `boot_index` - (Optional) The boot index of the volume. It defaults to 0. + +* `destination_type` - (Optional) The type that gets created. Possible values + are "volume" and "local". + ## Attributes Reference The following attributes are exported: From e2634562a4fd6288d5e81615d2da851cf0effbeb Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Sun, 1 Feb 2015 02:36:58 -0700 Subject: [PATCH 218/295] define block_device schema --- .../resource_openstack_compute_instance_v2.go | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 766c5dea8..17775ef3a 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -115,9 +115,33 @@ func resourceComputeInstanceV2() *schema.Resource { ForceNew: true, }, "block_device": &schema.Schema{ - Type: schema.TypeMap, + Type: schema.TypeList, Optional: true, ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "uuid": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "source_type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "volume_size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "destination_type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "boot_index": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + }, + }, }, }, } @@ -469,19 +493,13 @@ func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interfa sourceType := bootfromvolume.SourceType(bd["source_type"].(string)) bfvOpts := []bootfromvolume.BlockDevice{ bootfromvolume.BlockDevice{ - UUID: bd["uuid"].(string), - SourceType: sourceType, + UUID: bd["uuid"].(string), + SourceType: sourceType, + VolumeSize: bd["volume_size"].(int), + DestinationType: bd["destination_type"].(string), + BootIndex: bd["boot_index"].(int), }, } - if vs, ok := bd["volume_size"].(int); ok { - bfvOpts[0].VolumeSize = vs - } - if dt, ok := bd["destination_type"].(string); ok { - bfvOpts[0].DestinationType = dt - } - if bi, ok := bd["boot_index"].(int); ok { - bfvOpts[0].BootIndex = bi - } return bfvOpts } From ccd51ae3ab796e86558b74b17901c217e0291c8e Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Tue, 3 Feb 2015 20:30:10 -0700 Subject: [PATCH 219/295] added ok codes to gophercloud -> update ServerV2StateRefreshFunc --- ...source_openstack_blockstorage_volume_v1.go | 67 +++++++++++++++---- .../resource_openstack_compute_instance_v2.go | 10 ++- 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go index ee86b51d4..ea2d99a65 100644 --- a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go +++ b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" ) @@ -97,9 +98,9 @@ func resourceBlockStorageVolumeV1Create(d *schema.ResourceData, meta interface{} // Store the ID now d.SetId(v.ID) - // Wait for the volume to become running. + // Wait for the volume to become available. log.Printf( - "[DEBUG] Waiting for volume (%s) to become running", + "[DEBUG] Waiting for volume (%s) to become available", v.ID) stateConf := &resource.StateChangeConf{ @@ -132,16 +133,48 @@ func resourceBlockStorageVolumeV1Read(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error retrieving OpenStack volume: %s", err) } + log.Printf("\n\ngot volume: %+v\n\n", v) + log.Printf("[DEBUG] Retreived volume %s: %+v", d.Id(), v) d.Set("region", d.Get("region").(string)) - d.Set("description", v.Description) - d.Set("name", v.Name) d.Set("size", v.Size) - d.Set("snapshot_id", v.SnapshotID) - d.Set("source_vol_id", v.SourceVolID) - d.Set("volume_type", v.VolumeType) - d.Set("metadata", v.Metadata) + + if t, exists := d.GetOk("description"); exists && t != "" { + d.Set("description", v.Description) + } else { + d.Set("description", "") + } + + if t, exists := d.GetOk("name"); exists && t != "" { + d.Set("name", v.Name) + } else { + d.Set("name", "") + } + + if t, exists := d.GetOk("snapshot_id"); exists && t != "" { + d.Set("snapshot_id", v.SnapshotID) + } else { + d.Set("snapshot_id", "") + } + + if t, exists := d.GetOk("source_vol_id"); exists && t != "" { + d.Set("source_vol_id", v.SourceVolID) + } else { + d.Set("source_vol_id", "") + } + + if t, exists := d.GetOk("volume_type"); exists && t != "" { + d.Set("volume_type", v.VolumeType) + } else { + d.Set("volume_type", "") + } + + if t, exists := d.GetOk("metadata"); exists && t != "" { + d.Set("metadata", v.Metadata) + } else { + d.Set("metadata", "") + } return nil } @@ -187,7 +220,7 @@ func resourceBlockStorageVolumeV1Delete(d *schema.ResourceData, meta interface{} stateConf := &resource.StateChangeConf{ Pending: []string{"deleting"}, - Target: "", + Target: "deleted", Refresh: VolumeV1StateRefreshFunc(blockStorageClient, d.Id()), Timeout: 10 * time.Minute, Delay: 10 * time.Second, @@ -217,10 +250,18 @@ func resourceVolumeMetadataV1(d *schema.ResourceData) map[string]string { // an OpenStack volume. func VolumeV1StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - v, _ := volumes.Get(client, volumeID).Extract() - if v != nil { - return v, v.Status, nil + v, err := volumes.Get(client, volumeID).Extract() + if err != nil { + errCode, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok { + return nil, "", err + } + if errCode.Actual == 404 { + return v, "deleted", nil + } + return nil, "", err } - return nil, "", nil + + return v, v.Status, nil } } diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 17775ef3a..7b3db0c6c 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" + "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" @@ -427,7 +428,7 @@ func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) e log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id()) stateConf := &resource.StateChangeConf{ - Target: "", + Target: "DELETED", Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), Timeout: 10 * time.Minute, Delay: 10 * time.Second, @@ -451,6 +452,13 @@ func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID stri return func() (interface{}, string, error) { s, err := servers.Get(client, instanceID).Extract() if err != nil { + errCode, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok { + return nil, "", err + } + if errCode.Actual == 404 { + return s, "DELETED", nil + } return nil, "", err } From 8e9c6787dd048e422541342b9108c5c64ee2c5c8 Mon Sep 17 00:00:00 2001 From: Julien Vey Date: Wed, 4 Feb 2015 22:14:36 +0100 Subject: [PATCH 220/295] Just try the first IP available if none found before Some cloud don't implement correctly IP addresses. Instead of failing during the provisionning, we just take the first IP available and try with this one. --- .../resource_openstack_compute_instance_v2.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 7b3db0c6c..2d9a0cdb8 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -246,11 +246,25 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err pa := paRaw.(map[string]interface{}) if pa["version"].(float64) == 4 { host = pa["addr"].(string) - d.Set("access_ip_v4", host) + break } } } } + + // If no host found, just get the first IP we find + if host == "" { + for _, networkAddresses := range server.Addresses { + for _, element := range networkAddresses.([]interface{}) { + address := element.(map[string]interface{}) + if address["version"].(float64) == 4 { + host = address["addr"].(string) + break + } + } + } + } + d.Set("access_ip_v4", host) d.Set("host", host) log.Printf("host: %s", host) From aae87816f672e43aa41b5e3b77855feb817610e8 Mon Sep 17 00:00:00 2001 From: Julien Vey Date: Wed, 4 Feb 2015 22:15:11 +0100 Subject: [PATCH 221/295] add ACTIVE as pending state when deleting instance --- .../openstack/resource_openstack_compute_instance_v2.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 2d9a0cdb8..65c2b45da 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -442,6 +442,7 @@ func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) e log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id()) stateConf := &resource.StateChangeConf{ + Pending: []string{"ACTIVE"}, Target: "DELETED", Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), Timeout: 10 * time.Minute, From 9aa9c902482c3fd3131334737cd07d75bfc1b38b Mon Sep 17 00:00:00 2001 From: Julien Vey Date: Thu, 5 Feb 2015 08:23:46 +0100 Subject: [PATCH 222/295] Add floating IP resource --- builtin/providers/openstack/provider.go | 1 + ...urce_openstack_networking_floatingip_v2.go | 160 ++++++++++++++++++ ...openstack_networking_floatingip_v2_test.go | 89 ++++++++++ 3 files changed, 250 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go create mode 100644 builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 180b5c3a7..955101a44 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -68,6 +68,7 @@ func Provider() terraform.ResourceProvider { "openstack_lb_vip_v1": resourceLBVipV1(), "openstack_networking_network_v2": resourceNetworkingNetworkV2(), "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), + "openstack_networking_floatingip_v2": resourceNetworkingFloatingIPV2(), "openstack_objectstorage_container_v1": resourceObjectStorageContainerV1(), }, diff --git a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go new file mode 100644 index 000000000..410fb0e39 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go @@ -0,0 +1,160 @@ +package openstack + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/rackspace/gophercloud/openstack/networking/v2/networks" + "github.com/rackspace/gophercloud/pagination" +) + +func resourceNetworkingFloatingIPV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkFloatingIPV2Create, + Read: resourceNetworkFloatingIPV2Read, + Delete: resourceNetworkFloatingIPV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, + "address": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "pool": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceNetworkFloatingIPV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + poolID, err := getNetworkID(d, meta, d.Get("pool").(string)) + if err != nil { + return fmt.Errorf("Error retrieving floating IP pool name: %s", err) + } + if len(poolID) == 0 { + return fmt.Errorf("No network found with name: %s", d.Get("pool").(string)) + } + floatingIP, err := floatingips.Create(networkClient, floatingips.CreateOpts{ + FloatingNetworkID: poolID, + }).Extract() + if err != nil { + return fmt.Errorf("Error allocating floating IP: %s", err) + } + + d.SetId(floatingIP.ID) + + return resourceNetworkFloatingIPV2Read(d, meta) +} + +func resourceNetworkFloatingIPV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + floatingIP, err := floatingips.Get(networkClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error retrieving floatingIP: %s", err) + } + + d.Set("region", d.Get("region").(string)) + d.Set("address", floatingIP.FloatingIP) + poolName, err := getNetworkName(d, meta, floatingIP.FloatingNetworkID) + if err != nil { + return fmt.Errorf("Error retrieving floating IP pool name: %s", err) + } + d.Set("pool", poolName) + + return nil +} + +func resourceNetworkFloatingIPV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + err = floatingips.Delete(networkClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting floating IP: %s", err) + } + d.SetId("") + return nil +} + +func getNetworkID(d *schema.ResourceData, meta interface{}, networkName string) (string, error) { + config := meta.(*Config) + networkClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return "", fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + opts := networks.ListOpts{Name: networkName} + pager := networks.List(networkClient, opts) + networkID := "" + + err = pager.EachPage(func(page pagination.Page) (bool, error) { + networkList, err := networks.ExtractNetworks(page) + if err != nil { + return false, err + } + + for _, n := range networkList { + if n.Name == networkName { + networkID = n.ID + return false, nil + } + } + + return true, nil + }) + + return networkID, err +} + +func getNetworkName(d *schema.ResourceData, meta interface{}, networkID string) (string, error) { + config := meta.(*Config) + networkClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return "", fmt.Errorf("Error creating OpenStack network client: %s", err) + } + + opts := networks.ListOpts{ID: networkID} + pager := networks.List(networkClient, opts) + networkName := "" + + err = pager.EachPage(func(page pagination.Page) (bool, error) { + networkList, err := networks.ExtractNetworks(page) + if err != nil { + return false, err + } + + for _, n := range networkList { + if n.ID == networkID { + networkName = n.Name + return false, nil + } + } + + return true, nil + }) + + return networkName, err +} diff --git a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go new file mode 100644 index 000000000..390275928 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go @@ -0,0 +1,89 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" +) + +func TestAccNetworkingV2FloatingIP_basic(t *testing.T) { + var floatingIP floatingips.FloatingIP + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2FloatingIPDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkingV2FloatingIP_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2FloatingIPExists(t, "openstack_networking_floatingip_v2.foo", &floatingIP), + ), + }, + }, + }) +} + +func testAccCheckNetworkingV2FloatingIPDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2FloatingIPDestroy) Error creating OpenStack floating IP: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_networking_floatingip_v2" { + continue + } + + _, err := floatingips.Get(networkClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("FloatingIP still exists") + } + } + + return nil +} + +func testAccCheckNetworkingV2FloatingIPExists(t *testing.T, n string, kp *floatingips.FloatingIP) 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") + } + + config := testAccProvider.Meta().(*Config) + networkClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2FloatingIPExists) Error creating OpenStack networking client: %s", err) + } + + found, err := floatingips.Get(networkClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("FloatingIP not found") + } + + *kp = *found + + return nil + } +} + +var testAccNetworkingV2FloatingIP_basic = fmt.Sprintf(` + resource "openstack_networking_floatingip_v2" "foo" { + region = "%s" + pool = "PublicNetwork-01" + }`, + OS_REGION_NAME) From 760e03856e536f296ce4449aa1789148d96d8cba Mon Sep 17 00:00:00 2001 From: Julien Vey Date: Fri, 6 Feb 2015 14:34:11 +0100 Subject: [PATCH 223/295] Manage floating IP in compute instances --- .../resource_openstack_compute_instance_v2.go | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 65c2b45da..a5807100f 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -14,6 +14,9 @@ import ( "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" + "github.com/rackspace/gophercloud/openstack/networking/v2/networks" + "github.com/rackspace/gophercloud/openstack/networking/v2/ports" "github.com/rackspace/gophercloud/pagination" ) @@ -48,6 +51,11 @@ func resourceComputeInstanceV2() *schema.Resource { ForceNew: false, DefaultFunc: envDefaultFunc("OS_FLAVOR_ID"), }, + "floating_ip": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, "security_groups": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -215,6 +223,22 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e "Error waiting for instance (%s) to become ready: %s", server.ID, err) } + floatingIP := d.Get("floating_ip").(string) + if len(floatingIP) > 0 { + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + allFloatingIPs, err := getFloatingIPs(networkingClient) + if err != nil { + return fmt.Errorf("Error listing OpenStack floating IPs: %s", err) + } + err = assignFloatingIP(networkingClient, extractFloatingIPFromIP(allFloatingIPs, floatingIP), server.ID) + if err != nil { + fmt.Errorf("Error assigning floating IP to OpenStack compute instance: %s", err) + } + } return resourceComputeInstanceV2Read(d, meta) } @@ -375,6 +399,25 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e } } + if d.HasChange("floating_ip") { + floatingIP := d.Get("floating_ip").(string) + if len(floatingIP) > 0 { + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + allFloatingIPs, err := getFloatingIPs(networkingClient) + if err != nil { + return fmt.Errorf("Error listing OpenStack floating IPs: %s", err) + } + err = assignFloatingIP(networkingClient, extractFloatingIPFromIP(allFloatingIPs, floatingIP), d.Id()) + if err != nil { + fmt.Errorf("Error assigning floating IP to OpenStack compute instance: %s", err) + } + } + } + if d.HasChange("flavor_ref") { resizeOpts := &servers.ResizeOpts{ FlavorRef: d.Get("flavor_ref").(string), @@ -526,3 +569,93 @@ func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interfa return bfvOpts } + +func extractFloatingIPFromIP(ips []floatingips.FloatingIP, IP string) *floatingips.FloatingIP { + for _, floatingIP := range ips { + if floatingIP.FloatingIP == IP { + return &floatingIP + } + } + return nil +} + +func assignFloatingIP(networkingClient *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, instanceID string) error { + networkID, err := getFirstNetworkID(networkingClient, instanceID) + if err != nil { + return err + } + portID, err := getInstancePortID(networkingClient, instanceID, networkID) + _, err = floatingips.Update(networkingClient, floatingIP.ID, floatingips.UpdateOpts{ + PortID: portID, + }).Extract() + return err +} + +func getFirstNetworkID(networkingClient *gophercloud.ServiceClient, instanceID string) (string, error) { + pager := networks.List(networkingClient, networks.ListOpts{}) + + var networkdID string + err := pager.EachPage(func(page pagination.Page) (bool, error) { + networkList, err := networks.ExtractNetworks(page) + if err != nil { + return false, err + } + + if len(networkList) > 0 { + networkdID = networkList[0].ID + return false, nil + } + return false, fmt.Errorf("No network found for the instance %s", instanceID) + }) + if err != nil { + return "", err + } + return networkdID, nil + +} + +func getInstancePortID(networkingClient *gophercloud.ServiceClient, instanceID, networkID string) (string, error) { + pager := ports.List(networkingClient, ports.ListOpts{ + DeviceID: instanceID, + NetworkID: networkID, + }) + + var portID string + err := pager.EachPage(func(page pagination.Page) (bool, error) { + portList, err := ports.ExtractPorts(page) + if err != nil { + return false, err + } + for _, port := range portList { + portID = port.ID + return false, nil + } + return true, nil + }) + + if err != nil { + return "", err + } + return portID, nil +} + +func getFloatingIPs(networkingClient *gophercloud.ServiceClient) ([]floatingips.FloatingIP, error) { + pager := floatingips.List(networkingClient, floatingips.ListOpts{}) + + ips := []floatingips.FloatingIP{} + err := pager.EachPage(func(page pagination.Page) (bool, error) { + floatingipList, err := floatingips.ExtractFloatingIPs(page) + if err != nil { + return false, err + } + for _, f := range floatingipList { + ips = append(ips, f) + } + return true, nil + }) + + if err != nil { + return nil, err + } + return ips, nil +} From 132d5acb33a1a637d065cd2f35e12d53ba589361 Mon Sep 17 00:00:00 2001 From: Julien Vey Date: Mon, 9 Feb 2015 13:27:30 +0100 Subject: [PATCH 224/295] Make pool name configurable in tests --- builtin/providers/openstack/provider_test.go | 7 +++++++ .../openstack/resource_openstack_compute_instance_v2.go | 4 ++-- .../resource_openstack_networking_floatingip_v2_test.go | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/builtin/providers/openstack/provider_test.go b/builtin/providers/openstack/provider_test.go index 9e90bb4ea..d98ec1820 100644 --- a/builtin/providers/openstack/provider_test.go +++ b/builtin/providers/openstack/provider_test.go @@ -10,6 +10,7 @@ import ( var ( OS_REGION_NAME = "" + OS_POOL_NAME = "" ) var testAccProviders map[string]terraform.ResourceProvider @@ -49,6 +50,12 @@ func testAccPreCheck(t *testing.T) { t.Fatal("OS_IMAGE_ID must be set for acceptance tests") } + v = os.Getenv("OS_POOL_NAME") + if v == "" { + t.Fatal("OS_POOL_NAME must be set for acceptance tests") + } + OS_POOL_NAME = v + v = os.Getenv("OS_FLAVOR_ID") if v == "" { t.Fatal("OS_FLAVOR_ID must be set for acceptance tests") diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index a5807100f..bcf30a6ee 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -224,7 +224,7 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e server.ID, err) } floatingIP := d.Get("floating_ip").(string) - if len(floatingIP) > 0 { + if floatingIP != "" { networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) @@ -401,7 +401,7 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e if d.HasChange("floating_ip") { floatingIP := d.Get("floating_ip").(string) - if len(floatingIP) > 0 { + if floatingIP != "" { networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { return fmt.Errorf("Error creating OpenStack compute client: %s", err) diff --git a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go index 390275928..cd08ea512 100644 --- a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go @@ -84,6 +84,6 @@ func testAccCheckNetworkingV2FloatingIPExists(t *testing.T, n string, kp *floati var testAccNetworkingV2FloatingIP_basic = fmt.Sprintf(` resource "openstack_networking_floatingip_v2" "foo" { region = "%s" - pool = "PublicNetwork-01" + pool = "%s" }`, - OS_REGION_NAME) + OS_REGION_NAME, OS_POOL_NAME) From 32d0e36709b75844f697239c607245017013b385 Mon Sep 17 00:00:00 2001 From: Eric Bellemon Date: Mon, 9 Feb 2015 23:37:39 +0100 Subject: [PATCH 225/295] Add router resource --- builtin/providers/openstack/provider.go | 1 + ...resource_openstack_networking_router_v2.go | 190 ++++++++++++++++++ ...rce_openstack_networking_router_v2_test.go | 104 ++++++++++ 3 files changed, 295 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_networking_router_v2.go create mode 100644 builtin/providers/openstack/resource_openstack_networking_router_v2_test.go diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 955101a44..64e87cbb4 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -69,6 +69,7 @@ func Provider() terraform.ResourceProvider { "openstack_networking_network_v2": resourceNetworkingNetworkV2(), "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), "openstack_networking_floatingip_v2": resourceNetworkingFloatingIPV2(), + "openstack_networking_router_v2": resourceNetworkingRouterV2(), "openstack_objectstorage_container_v1": resourceObjectStorageContainerV1(), }, diff --git a/builtin/providers/openstack/resource_openstack_networking_router_v2.go b/builtin/providers/openstack/resource_openstack_networking_router_v2.go new file mode 100644 index 000000000..c4fe8e90a --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_router_v2.go @@ -0,0 +1,190 @@ +package openstack + +import ( + "fmt" + "log" + "strconv" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/racker/perigee" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" +) + +func resourceNetworkingRouterV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingRouterV2Create, + Read: resourceNetworkingRouterV2Read, + Update: resourceNetworkingRouterV2Update, + Delete: resourceNetworkingRouterV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "admin_state_up": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "external_gateway": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + }, + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + createOpts := routers.CreateOpts{ + Name: d.Get("name").(string), + TenantID: d.Get("tenant_id").(string), + } + + asuRaw := d.Get("admin_state_up").(string) + if asuRaw != "" { + asu, err := strconv.ParseBool(asuRaw) + if err != nil { + return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") + } + createOpts.AdminStateUp = &asu + } + + externalGateway := d.Get("external_gateway").(string) + if externalGateway != "" { + gatewayInfo := routers.GatewayInfo{ + NetworkID: externalGateway, + } + createOpts.GatewayInfo = &gatewayInfo + } + + log.Printf("[INFO] Requesting router creation") + n, err := routers.Create(networkingClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack Neutron router: %s", err) + } + log.Printf("[INFO] Router ID: %s", n.ID) + + d.SetId(n.ID) + + return resourceNetworkingRouterV2Read(d, meta) +} + +func resourceNetworkingRouterV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + n, err := routers.Get(networkingClient, d.Id()).Extract() + if err != nil { + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok { + return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err) + } + + if httpError.Actual == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err) + } + + log.Printf("[DEBUG] Retreived Router %s: %+v", d.Id(), n) + + d.Set("region", d.Get("region").(string)) + + if t, exists := d.GetOk("name"); exists && t != "" { + d.Set("name", n.Name) + } else { + d.Set("name", "") + } + + if t, exists := d.GetOk("admin_state_up"); exists && t != "" { + d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp)) + } else { + d.Set("admin_state_up", "") + } + + if t, exists := d.GetOk("tenant_id"); exists && t != "" { + d.Set("tenant_id", n.TenantID) + } else { + d.Set("tenant_id", "") + } + + if t, exists := d.GetOk("external_gateway"); exists && t != "" { + d.Set("external_gateway", n.GatewayInfo.NetworkID) + } else { + d.Set("external_gateway", "") + } + + return nil +} + +func resourceNetworkingRouterV2Update(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + var updateOpts routers.UpdateOpts + if d.HasChange("name") { + updateOpts.Name = d.Get("name").(string) + } + if d.HasChange("admin_state_up") { + asuRaw := d.Get("admin_state_up").(string) + if asuRaw != "" { + asu, err := strconv.ParseBool(asuRaw) + if err != nil { + return fmt.Errorf("admin_state_up, if provided, must be either 'true' or 'false'") + } + updateOpts.AdminStateUp = &asu + } + } + + log.Printf("[DEBUG] Updating Router %s with options: %+v", d.Id(), updateOpts) + + _, err = routers.Update(networkingClient, d.Id(), updateOpts).Extract() + if err != nil { + return fmt.Errorf("Error updating OpenStack Neutron Router: %s", err) + } + + return resourceNetworkingRouterV2Read(d, meta) +} + +func resourceNetworkingRouterV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + err = routers.Delete(networkingClient, d.Id()).ExtractErr() + if err != nil { + return fmt.Errorf("Error deleting OpenStack Neutron Router: %s", err) + } + + d.SetId("") + return nil +} diff --git a/builtin/providers/openstack/resource_openstack_networking_router_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_router_v2_test.go new file mode 100644 index 000000000..57356cb74 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_router_v2_test.go @@ -0,0 +1,104 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" +) + +func TestAccNetworkingV2Router_basic(t *testing.T) { + var router routers.Router + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2RouterDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkingV2Router_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2RouterExists(t, "openstack_networking_router_v2.foo", &router), + ), + }, + resource.TestStep{ + Config: testAccNetworkingV2Router_update, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("openstack_networking_router_v2.foo", "name", "router_2"), + ), + }, + }, + }) +} + +func testAccCheckNetworkingV2RouterDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2RouterDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_networking_router_v2" { + continue + } + + _, err := routers.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Router still exists") + } + } + + return nil +} + +func testAccCheckNetworkingV2RouterExists(t *testing.T, n string, router *routers.Router) 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") + } + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2RouterExists) Error creating OpenStack networking client: %s", err) + } + + found, err := routers.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Router not found") + } + + *router = *found + + return nil + } +} + +var testAccNetworkingV2Router_basic = fmt.Sprintf(` + resource "openstack_networking_router_v2" "foo" { + name = "router" + region = "%s" + admin_state_up = "true" + }`, + OS_REGION_NAME) + +var testAccNetworkingV2Router_update = fmt.Sprintf(` + resource "openstack_networking_router_v2" "foo" { + region = "%s" + name = "router_2" + admin_state_up = "true" + }`, + OS_REGION_NAME) From e9abf04e4b750a6d078be187e50918bb1435b290 Mon Sep 17 00:00:00 2001 From: Eric Bellemon Date: Mon, 9 Feb 2015 23:37:58 +0100 Subject: [PATCH 226/295] Add router interface resource --- builtin/providers/openstack/provider.go | 25 ++-- ...penstack_networking_router_interface_v2.go | 109 ++++++++++++++++++ ...ack_networking_router_interface_v2_test.go | 104 +++++++++++++++++ 3 files changed, 226 insertions(+), 12 deletions(-) create mode 100644 builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go create mode 100644 builtin/providers/openstack/resource_openstack_networking_router_interface_v2_test.go diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 64e87cbb4..450e1c705 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -59,18 +59,19 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "openstack_blockstorage_volume_v1": resourceBlockStorageVolumeV1(), - "openstack_compute_instance_v2": resourceComputeInstanceV2(), - "openstack_compute_keypair_v2": resourceComputeKeypairV2(), - "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), - "openstack_lb_monitor_v1": resourceLBMonitorV1(), - "openstack_lb_pool_v1": resourceLBPoolV1(), - "openstack_lb_vip_v1": resourceLBVipV1(), - "openstack_networking_network_v2": resourceNetworkingNetworkV2(), - "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), - "openstack_networking_floatingip_v2": resourceNetworkingFloatingIPV2(), - "openstack_networking_router_v2": resourceNetworkingRouterV2(), - "openstack_objectstorage_container_v1": resourceObjectStorageContainerV1(), + "openstack_blockstorage_volume_v1": resourceBlockStorageVolumeV1(), + "openstack_compute_instance_v2": resourceComputeInstanceV2(), + "openstack_compute_keypair_v2": resourceComputeKeypairV2(), + "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), + "openstack_lb_monitor_v1": resourceLBMonitorV1(), + "openstack_lb_pool_v1": resourceLBPoolV1(), + "openstack_lb_vip_v1": resourceLBVipV1(), + "openstack_networking_network_v2": resourceNetworkingNetworkV2(), + "openstack_networking_subnet_v2": resourceNetworkingSubnetV2(), + "openstack_networking_floatingip_v2": resourceNetworkingFloatingIPV2(), + "openstack_networking_router_v2": resourceNetworkingRouterV2(), + "openstack_networking_router_interface_v2": resourceNetworkingRouterInterfaceV2(), + "openstack_objectstorage_container_v1": resourceObjectStorageContainerV1(), }, ConfigureFunc: configureProvider, diff --git a/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go new file mode 100644 index 000000000..2daaf787d --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go @@ -0,0 +1,109 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/racker/perigee" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" + "github.com/rackspace/gophercloud/openstack/networking/v2/ports" +) + +func resourceNetworkingRouterInterfaceV2() *schema.Resource { + return &schema.Resource{ + Create: resourceNetworkingRouterInterfaceV2Create, + Read: resourceNetworkingRouterInterfaceV2Read, + Delete: resourceNetworkingRouterInterfaceV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, + "router_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "subnet_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceNetworkingRouterInterfaceV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + createOpts := routers.InterfaceOpts{ + SubnetID: d.Get("subnet_id").(string), + } + + log.Printf("[INFO] Requesting router interface creation") + n, err := routers.AddInterface(networkingClient, d.Get("router_id").(string), createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating OpenStack Neutron router interface: %s", err) + } + log.Printf("[INFO] Router interface Port ID: %s", n.PortID) + + d.SetId(n.PortID) + + return resourceNetworkingRouterInterfaceV2Read(d, meta) +} + +func resourceNetworkingRouterInterfaceV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + n, err := ports.Get(networkingClient, d.Id()).Extract() + if err != nil { + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok { + return fmt.Errorf("Error retrieving OpenStack Neutron Router Interface: %s", err) + } + + if httpError.Actual == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("Error retrieving OpenStack Neutron Router Interface: %s", err) + } + + log.Printf("[DEBUG] Retreived Router Interface %s: %+v", d.Id(), n) + + d.Set("region", d.Get("region").(string)) + + return nil +} + +func resourceNetworkingRouterInterfaceV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + removeOpts := routers.InterfaceOpts{ + SubnetID: d.Get("subnet_id").(string), + } + + _, err = routers.RemoveInterface(networkingClient, d.Get("router_id").(string), removeOpts).Extract() + if err != nil { + return fmt.Errorf("Error deleting OpenStack Neutron Router Interface: %s", err) + } + + d.SetId("") + return nil +} diff --git a/builtin/providers/openstack/resource_openstack_networking_router_interface_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2_test.go new file mode 100644 index 000000000..a74dbc30b --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2_test.go @@ -0,0 +1,104 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/networking/v2/ports" +) + +func TestAccNetworkingV2RouterInterface_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNetworkingV2RouterInterfaceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNetworkingV2RouterInterface_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkingV2RouterInterfaceExists(t, "openstack_networking_router_interface_v2.int_1"), + ), + }, + }, + }) +} + +func testAccCheckNetworkingV2RouterInterfaceDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2RouterInterfaceDestroy) Error creating OpenStack networking client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_networking_router_interface_v2" { + continue + } + + _, err := ports.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Router interface still exists") + } + } + + return nil +} + +func testAccCheckNetworkingV2RouterInterfaceExists(t *testing.T, n string) 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") + } + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckNetworkingV2RouterInterfaceExists) Error creating OpenStack networking client: %s", err) + } + + found, err := ports.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("Router interface not found") + } + + return nil + } +} + +var testAccNetworkingV2RouterInterface_basic = fmt.Sprintf(` + resource "openstack_networking_router_v2" "router_1" { + name = "router_1" + region = "%s" + admin_state_up = "true" + } + + resource "openstack_networking_router_interface_v2" "int_1" { + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + router_id = "${openstack_networking_router_v2.router_1.id}" + region = "%s" + } + + resource "openstack_networking_network_v2" "network_1" { + region = "%s" + name = "network_1" + admin_state_up = "true" + } + + resource "openstack_networking_subnet_v2" "subnet_1" { + region = "%s" + network_id = "${openstack_networking_network_v2.network_1.id}" + cidr = "192.168.199.0/24" + ip_version = 4 + }`, OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME) From 9b30ef4eb2c8284364b5ed18869cdba572f0b2fd Mon Sep 17 00:00:00 2001 From: Eric Bellemon Date: Mon, 9 Feb 2015 23:55:21 +0100 Subject: [PATCH 227/295] Remove region properties on acceptance tests --- ...ack_networking_router_interface_v2_test.go | 38 +++++++++---------- ...rce_openstack_networking_router_v2_test.go | 20 ++++------ 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_networking_router_interface_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2_test.go index a74dbc30b..be3b12c0b 100644 --- a/builtin/providers/openstack/resource_openstack_networking_router_interface_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2_test.go @@ -78,27 +78,23 @@ func testAccCheckNetworkingV2RouterInterfaceExists(t *testing.T, n string) resou } var testAccNetworkingV2RouterInterface_basic = fmt.Sprintf(` - resource "openstack_networking_router_v2" "router_1" { - name = "router_1" - region = "%s" - admin_state_up = "true" - } +resource "openstack_networking_router_v2" "router_1" { + name = "router_1" + admin_state_up = "true" +} - resource "openstack_networking_router_interface_v2" "int_1" { - subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" - router_id = "${openstack_networking_router_v2.router_1.id}" - region = "%s" - } +resource "openstack_networking_router_interface_v2" "int_1" { + subnet_id = "${openstack_networking_subnet_v2.subnet_1.id}" + router_id = "${openstack_networking_router_v2.router_1.id}" +} - resource "openstack_networking_network_v2" "network_1" { - region = "%s" - name = "network_1" - admin_state_up = "true" - } +resource "openstack_networking_network_v2" "network_1" { + name = "network_1" + admin_state_up = "true" +} - resource "openstack_networking_subnet_v2" "subnet_1" { - region = "%s" - network_id = "${openstack_networking_network_v2.network_1.id}" - cidr = "192.168.199.0/24" - ip_version = 4 - }`, OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME, OS_REGION_NAME) +resource "openstack_networking_subnet_v2" "subnet_1" { + network_id = "${openstack_networking_network_v2.network_1.id}" + cidr = "192.168.199.0/24" + ip_version = 4 +}`) diff --git a/builtin/providers/openstack/resource_openstack_networking_router_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_router_v2_test.go index 57356cb74..248f4e721 100644 --- a/builtin/providers/openstack/resource_openstack_networking_router_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_router_v2_test.go @@ -88,17 +88,13 @@ func testAccCheckNetworkingV2RouterExists(t *testing.T, n string, router *router } var testAccNetworkingV2Router_basic = fmt.Sprintf(` - resource "openstack_networking_router_v2" "foo" { - name = "router" - region = "%s" - admin_state_up = "true" - }`, - OS_REGION_NAME) + resource "openstack_networking_router_v2" "foo" { + name = "router" + admin_state_up = "true" + }`) var testAccNetworkingV2Router_update = fmt.Sprintf(` - resource "openstack_networking_router_v2" "foo" { - region = "%s" - name = "router_2" - admin_state_up = "true" - }`, - OS_REGION_NAME) + resource "openstack_networking_router_v2" "foo" { + name = "router_2" + admin_state_up = "true" + }`) From fafa9468711f9ce1c851afdce06a80c9f17fd9bb Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Mon, 9 Feb 2015 20:27:39 -0700 Subject: [PATCH 228/295] handle 404 (Not Found) in Get operations --- ...source_openstack_blockstorage_volume_v1.go | 2 +- .../resource_openstack_compute_instance_v2.go | 2 +- .../resource_openstack_compute_keypair_v2.go | 2 +- .../resource_openstack_compute_secgroup_v2.go | 2 +- .../resource_openstack_lb_monitor_v1.go | 2 +- .../resource_openstack_lb_pool_v1.go | 2 +- .../openstack/resource_openstack_lb_vip_v1.go | 2 +- ...urce_openstack_networking_floatingip_v2.go | 2 +- ...esource_openstack_networking_network_v2.go | 2 +- ...resource_openstack_networking_subnet_v2.go | 2 +- builtin/providers/openstack/util.go | 22 +++++++++++++++++++ 11 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 builtin/providers/openstack/util.go diff --git a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go index ea2d99a65..ab85317bb 100644 --- a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go +++ b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go @@ -130,7 +130,7 @@ func resourceBlockStorageVolumeV1Read(d *schema.ResourceData, meta interface{}) v, err := volumes.Get(blockStorageClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error retrieving OpenStack volume: %s", err) + return CheckDeleted(d, err, "volume") } log.Printf("\n\ngot volume: %+v\n\n", v) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index bcf30a6ee..a60279c3d 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -252,7 +252,7 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err server, err := servers.Get(computeClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error retrieving OpenStack server: %s", err) + return CheckDeleted(d, err, "server") } log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server) diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go index 8cdc84913..9c1417412 100644 --- a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go @@ -65,7 +65,7 @@ func resourceComputeKeypairV2Read(d *schema.ResourceData, meta interface{}) erro kp, err := keypairs.Get(computeClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error retrieving OpenStack keypair: %s", err) + return CheckDeleted(d, err, "keypair") } d.Set("region", d.Get("region").(string)) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index f1d9ae714..cb61e7458 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -111,7 +111,7 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err sg, err := secgroups.Get(computeClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error retrieving OpenStack security group: %s", err) + return CheckDeleted(d, err, "security group") } d.Set("region", d.Get("region").(string)) diff --git a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go index 7483b17e4..0761ec1f4 100644 --- a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go @@ -120,7 +120,7 @@ func resourceLBMonitorV1Read(d *schema.ResourceData, meta interface{}) error { m, err := monitors.Get(networkingClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error retrieving OpenStack LB Monitor: %s", err) + return CheckDeleted(d, err, "LB monitor") } log.Printf("[DEBUG] Retreived OpenStack LB Monitor %s: %+v", d.Id(), m) diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go index a7b5abbaf..7e3abdbf7 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go @@ -154,7 +154,7 @@ func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error { p, err := pools.Get(networkingClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error retrieving OpenStack LB Pool: %s", err) + return CheckDeleted(d, err, "LB pool") } log.Printf("[DEBUG] Retreived OpenStack LB Pool %s: %+v", d.Id(), p) diff --git a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go index cef3977c8..6181bc8a5 100644 --- a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go @@ -133,7 +133,7 @@ func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error { p, err := vips.Get(networkingClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error retrieving OpenStack LB VIP: %s", err) + return CheckDeleted(d, err, "LB VIP") } log.Printf("[DEBUG] Retreived OpenStack LB VIP %s: %+v", d.Id(), p) diff --git a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go index 410fb0e39..fb4965768 100644 --- a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go @@ -70,7 +70,7 @@ func resourceNetworkFloatingIPV2Read(d *schema.ResourceData, meta interface{}) e floatingIP, err := floatingips.Get(networkClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error retrieving floatingIP: %s", err) + return CheckDeleted(d, err, "floating IP") } d.Set("region", d.Get("region").(string)) diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2.go b/builtin/providers/openstack/resource_openstack_networking_network_v2.go index fc420b2e9..bf556f418 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2.go @@ -98,7 +98,7 @@ func resourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) e n, err := networks.Get(networkingClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error retrieving OpenStack Neutron Network: %s", err) + return CheckDeleted(d, err, "network") } log.Printf("[DEBUG] Retreived Network %s: %+v", d.Id(), n) diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go index 74a43e27c..8462611df 100644 --- a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go @@ -155,7 +155,7 @@ func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) er s, err := subnets.Get(networkingClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error retrieving OpenStack Neutron Subnet: %s", err) + return CheckDeleted(d, err, "subnet") } log.Printf("[DEBUG] Retreived Subnet %s: %+v", d.Id(), s) diff --git a/builtin/providers/openstack/util.go b/builtin/providers/openstack/util.go new file mode 100644 index 000000000..7ed9f8f5f --- /dev/null +++ b/builtin/providers/openstack/util.go @@ -0,0 +1,22 @@ +package openstack + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/racker/perigee" +) + +// CheckDeleted checks the error to see if it's a 404 (Not Found) and, if so, +// sets the resource ID to the empty string instead of throwing an error. +func CheckDeleted(d *schema.ResourceData, err error, resource string) error { + errCode, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok { + return fmt.Errorf("Error retrieving OpenStack %s: %s", resource, err) + } + if errCode.Actual == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("Error retrieving OpenStack %s: %s", resource, err) +} From f51a53000f54ef180a1f3a3e4cd30628b72c769c Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Tue, 10 Feb 2015 05:20:23 +0000 Subject: [PATCH 229/295] Support for image_name This commit renames image_ref to image_id and adds the image_name parameter. Users can now specify either an image UUID or image name when launching instances. image_name is preferrable as deployers/sysadmins generally regularly deprecate/remove outdated and insecure images. Using a consistent naming scheme allows end-users to always retrieve a working image. --- builtin/providers/openstack/provider_test.go | 8 +-- .../resource_openstack_compute_instance_v2.go | 51 +++++++++++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/builtin/providers/openstack/provider_test.go b/builtin/providers/openstack/provider_test.go index d98ec1820..cafda7ef8 100644 --- a/builtin/providers/openstack/provider_test.go +++ b/builtin/providers/openstack/provider_test.go @@ -45,9 +45,11 @@ func testAccPreCheck(t *testing.T) { } OS_REGION_NAME = v - v = os.Getenv("OS_IMAGE_ID") - if v == "" { - t.Fatal("OS_IMAGE_ID must be set for acceptance tests") + v1 := os.Getenv("OS_IMAGE_ID") + v2 := os.Getenv("OS_IMAGE_NAME") + + if v1 == "" && v2 == "" { + t.Fatal("OS_IMAGE_ID or OS_IMAGE_NAME must be set for acceptance tests") } v = os.Getenv("OS_POOL_NAME") diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index a60279c3d..c56f86792 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -13,6 +13,7 @@ import ( "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" + "github.com/rackspace/gophercloud/openstack/compute/v2/images" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" "github.com/rackspace/gophercloud/openstack/networking/v2/networks" @@ -39,12 +40,18 @@ func resourceComputeInstanceV2() *schema.Resource { Required: true, ForceNew: false, }, - "image_ref": &schema.Schema{ + "image_id": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: false, + ForceNew: true, DefaultFunc: envDefaultFunc("OS_IMAGE_ID"), }, + "image_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_IMAGE_NAME"), + }, "flavor_ref": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -165,9 +172,14 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e var createOpts servers.CreateOptsBuilder + imageId, err := getImageID(computeClient, d) + if err != nil { + return err + } + createOpts = &servers.CreateOpts{ Name: d.Get("name").(string), - ImageRef: d.Get("image_ref").(string), + ImageRef: imageId, FlavorRef: d.Get("flavor_ref").(string), SecurityGroups: resourceInstanceSecGroupsV2(d), AvailabilityZone: d.Get("availability_zone").(string), @@ -659,3 +671,36 @@ func getFloatingIPs(networkingClient *gophercloud.ServiceClient) ([]floatingips. } return ips, nil } + +func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { + imageID := d.Get("image_id").(string) + imageName := d.Get("image_name").(string) + if imageID == "" { + pager := images.ListDetail(client, nil) + + pager.EachPage(func(page pagination.Page) (bool, error) { + imageList, err := images.ExtractImages(page) + + if err != nil { + return false, err + } + + for _, i := range imageList { + if i.Name == imageName { + imageID = i.ID + } + } + return true, nil + }) + + if imageID == "" { + return "", fmt.Errorf("Unable to find image: %v", imageName) + } + } + + if imageID == "" && imageName == "" { + return "", fmt.Errorf("Neither an image ID nor an image name were able to be determined.") + } + + return imageID, nil +} From bad2c9f18d5038c9b6377c916dec2f46cc500479 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Tue, 10 Feb 2015 16:58:27 +0000 Subject: [PATCH 230/295] Accounting for multiple results of an image name If multiple results are found, an error will be returned to the user. --- .../resource_openstack_compute_instance_v2.go | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index c56f86792..b4f51cc1d 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -673,34 +673,43 @@ func getFloatingIPs(networkingClient *gophercloud.ServiceClient) ([]floatingips. } func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { - imageID := d.Get("image_id").(string) + imageId := d.Get("image_id").(string) imageName := d.Get("image_name").(string) - if imageID == "" { - pager := images.ListDetail(client, nil) + imageCount := 0 + if imageId == "" && imageName != "" { + pager := images.ListDetail(client, &images.ListOpts{ + Name: imageName, + }) pager.EachPage(func(page pagination.Page) (bool, error) { imageList, err := images.ExtractImages(page) - if err != nil { return false, err } for _, i := range imageList { if i.Name == imageName { - imageID = i.ID + imageCount++ + imageId = i.ID } } return true, nil }) - if imageID == "" { - return "", fmt.Errorf("Unable to find image: %v", imageName) + switch imageCount { + case 0: + return "", fmt.Errorf("Unable to find image: %s", imageName) + case 1: + return imageId, nil + default: + return "", fmt.Errorf("Found %d images matching %s", imageCount, imageName) } } - if imageID == "" && imageName == "" { + if imageId == "" && imageName == "" { return "", fmt.Errorf("Neither an image ID nor an image name were able to be determined.") } - return imageID, nil + return imageId, nil + } From 74482abc5bf300c6d909ffd9ffd17be6ba04ba72 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Wed, 11 Feb 2015 04:11:04 +0000 Subject: [PATCH 231/295] Refactoring multiple results --- .../resource_openstack_compute_instance_v2.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index b4f51cc1d..5b03577f4 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -674,10 +674,14 @@ func getFloatingIPs(networkingClient *gophercloud.ServiceClient) ([]floatingips. func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { imageId := d.Get("image_id").(string) - imageName := d.Get("image_name").(string) - imageCount := 0 - if imageId == "" && imageName != "" { + if imageId != "" { + return imageId, nil + } + + imageCount := 0 + imageName := d.Get("image_name").(string) + if imageName != "" { pager := images.ListDetail(client, &images.ListOpts{ Name: imageName, }) @@ -705,11 +709,5 @@ func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (stri return "", fmt.Errorf("Found %d images matching %s", imageCount, imageName) } } - - if imageId == "" && imageName == "" { - return "", fmt.Errorf("Neither an image ID nor an image name were able to be determined.") - } - - return imageId, nil - + return "", fmt.Errorf("Neither an image ID nor an image name were able to be determined.") } From 2b5c7c6e2c9c72c12b349552e3e405e9a456a38c Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Wed, 11 Feb 2015 04:14:45 +0000 Subject: [PATCH 232/295] Updated documentation to reflect the image_ref / image_id change. --- .../openstack/r/compute_instance_v2.html.markdown | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown index 9095cb15a..f6cf6401c 100644 --- a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown @@ -15,7 +15,7 @@ Manages a V2 VM instance resource within OpenStack. ``` resource "openstack_compute_instance_v2" "test-server" { name = "tf-test" - image_ref = "ad091b52-742f-469e-8f3c-fd81cadf0743" + image_id = "ad091b52-742f-469e-8f3c-fd81cadf0743" flavor_ref = "3" metadata { this = "that" @@ -35,8 +35,13 @@ The following arguments are supported: * `name` - (Required) A unique name for the resource. -* `image_ref` - (Required) The image reference (ID) for the desired image for - the server. Changing this creates a new server. +* `image_id` - (Required) The image reference (ID) for the desired image for + the server. Changing this creates a new server. Note that `image_id` and + `image_name` are mutually exclusive. + +* `image_name` - (Required) The image name for the server. Changing this + creates a new server. Note that `image_id` and `image_name` are mutually + exclusive. * `flavor_ref` - (Required) The flavor reference (ID) for the desired flavor for the server. Changing this resizes the existing server. From 6f8df3d34ef8f1ef3e1a5d0260d58f60d68d5fcc Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Wed, 11 Feb 2015 04:22:25 +0000 Subject: [PATCH 233/295] Doc touchup --- .../providers/openstack/r/compute_instance_v2.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown index f6cf6401c..542210dc6 100644 --- a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown @@ -35,9 +35,9 @@ The following arguments are supported: * `name` - (Required) A unique name for the resource. -* `image_id` - (Required) The image reference (ID) for the desired image for - the server. Changing this creates a new server. Note that `image_id` and - `image_name` are mutually exclusive. +* `image_id` - (Required) The image ID of the desired image for the server. + Changing this creates a new server. Note that `image_id` and `image_name` + are mutually exclusive. * `image_name` - (Required) The image name for the server. Changing this creates a new server. Note that `image_id` and `image_name` are mutually From 52102624c68acc946aeb5c5c559c4c51febb74b3 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Wed, 11 Feb 2015 04:27:03 +0000 Subject: [PATCH 234/295] More doc touchups --- .../openstack/r/compute_instance_v2.html.markdown | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown index 542210dc6..03091df0f 100644 --- a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown @@ -35,13 +35,11 @@ The following arguments are supported: * `name` - (Required) A unique name for the resource. -* `image_id` - (Required) The image ID of the desired image for the server. - Changing this creates a new server. Note that `image_id` and `image_name` - are mutually exclusive. +* `image_id` - (Optional; Required if `image_name` is empty) The image ID of + the desired image for the server. Changing this creates a new server. -* `image_name` - (Required) The image name for the server. Changing this - creates a new server. Note that `image_id` and `image_name` are mutually - exclusive. +* `image_name` - (Optional; Required if `image_id` is empty) The name of the + desired image for the server. Changing this creates a new server. * `flavor_ref` - (Required) The flavor reference (ID) for the desired flavor for the server. Changing this resizes the existing server. From 768292c069c246a6b87455a576ee9755b597171f Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Wed, 11 Feb 2015 05:24:38 +0000 Subject: [PATCH 235/295] Support for flavor_name This commit renames flavor_ref to flavor_id and adds the flavor_name parameter. Users can now specify either a flavor ID or name when launching instances. --- builtin/providers/openstack/provider_test.go | 7 +- .../resource_openstack_compute_instance_v2.go | 66 +++++++++++++++++-- .../r/compute_instance_v2.html.markdown | 9 ++- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/builtin/providers/openstack/provider_test.go b/builtin/providers/openstack/provider_test.go index cafda7ef8..7b3e65dd4 100644 --- a/builtin/providers/openstack/provider_test.go +++ b/builtin/providers/openstack/provider_test.go @@ -58,8 +58,9 @@ func testAccPreCheck(t *testing.T) { } OS_POOL_NAME = v - v = os.Getenv("OS_FLAVOR_ID") - if v == "" { - t.Fatal("OS_FLAVOR_ID must be set for acceptance tests") + v1 = os.Getenv("OS_FLAVOR_ID") + v2 = os.Getenv("OS_FLAVOR_NAME") + if v1 == "" && v2 == "" { + t.Fatal("OS_FLAVOR_ID or OS_FLAVOR_NAME must be set for acceptance tests") } } diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 5b03577f4..0a7b1b624 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -13,6 +13,7 @@ import ( "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" + "github.com/rackspace/gophercloud/openstack/compute/v2/flavors" "github.com/rackspace/gophercloud/openstack/compute/v2/images" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" @@ -52,12 +53,20 @@ func resourceComputeInstanceV2() *schema.Resource { ForceNew: true, DefaultFunc: envDefaultFunc("OS_IMAGE_NAME"), }, - "flavor_ref": &schema.Schema{ + "flavor_id": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: false, + Computed: true, DefaultFunc: envDefaultFunc("OS_FLAVOR_ID"), }, + "flavor_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: false, + Computed: true, + DefaultFunc: envDefaultFunc("OS_FLAVOR_NAME"), + }, "floating_ip": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -177,10 +186,15 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e return err } + flavorId, err := getFlavorID(computeClient, d) + if err != nil { + return err + } + createOpts = &servers.CreateOpts{ Name: d.Get("name").(string), ImageRef: imageId, - FlavorRef: d.Get("flavor_ref").(string), + FlavorRef: flavorId, SecurityGroups: resourceInstanceSecGroupsV2(d), AvailabilityZone: d.Get("availability_zone").(string), Networks: resourceInstanceNetworksV2(d), @@ -327,11 +341,17 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err }) d.Set("security_groups.#", secGrpNum) - newFlavor, ok := server.Flavor["id"].(string) + flavorId, ok := server.Flavor["id"].(string) if !ok { return fmt.Errorf("Error setting OpenStack server's flavor: %v", server.Flavor) } - d.Set("flavor_ref", newFlavor) + d.Set("flavor_id", flavorId) + + flavor, err := flavors.Get(computeClient, flavorId).Extract() + if err != nil { + return err + } + d.Set("flavor_name", flavor.Name) return nil } @@ -711,3 +731,41 @@ func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (stri } return "", fmt.Errorf("Neither an image ID nor an image name were able to be determined.") } + +func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { + flavorId := d.Get("flavor_id").(string) + + if flavorId != "" { + return flavorId, nil + } + + flavorCount := 0 + flavorName := d.Get("flavor_name").(string) + if flavorName != "" { + pager := flavors.ListDetail(client, nil) + pager.EachPage(func(page pagination.Page) (bool, error) { + flavorList, err := flavors.ExtractFlavors(page) + if err != nil { + return false, err + } + + for _, f := range flavorList { + if f.Name == flavorName { + flavorCount++ + flavorId = f.ID + } + } + return true, nil + }) + + switch flavorCount { + case 0: + return "", fmt.Errorf("Unable to find flavor: %s", flavorName) + case 1: + return flavorId, nil + default: + return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, flavorName) + } + } + return "", fmt.Errorf("Neither an flavor ID nor an flavor name were able to be determined.") +} diff --git a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown index 03091df0f..ba03ee01c 100644 --- a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown @@ -16,7 +16,7 @@ Manages a V2 VM instance resource within OpenStack. resource "openstack_compute_instance_v2" "test-server" { name = "tf-test" image_id = "ad091b52-742f-469e-8f3c-fd81cadf0743" - flavor_ref = "3" + flavor_id = "3" metadata { this = "that" } @@ -41,8 +41,11 @@ The following arguments are supported: * `image_name` - (Optional; Required if `image_id` is empty) The name of the desired image for the server. Changing this creates a new server. -* `flavor_ref` - (Required) The flavor reference (ID) for the desired flavor - for the server. Changing this resizes the existing server. +* `flavor_id` - (Optional; Required if `flavor_name` is empty) The flavor ID of + the desired flavor for the server. Changing this resizes the existing server. + +* `flavor_name` - (Optional; Required if `flavor_id` is empty) The name of the + desired flavor for the server. Changing this resizes the existing server. * `security_groups` - (Optional) An array of one or more security group names to associate with the server. Changing this results in adding/removing From b3438d07d69fd3d92ad865a07d8e116ae5a0da60 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Wed, 11 Feb 2015 05:33:04 +0000 Subject: [PATCH 236/295] This commit enables both the image_id and the image_name to be computed, so that specifying one will populate the other. --- .../resource_openstack_compute_instance_v2.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 0a7b1b624..fbc7a7dd7 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -45,12 +45,14 @@ func resourceComputeInstanceV2() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, + Computed: true, DefaultFunc: envDefaultFunc("OS_IMAGE_ID"), }, "image_name": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, + Computed: true, DefaultFunc: envDefaultFunc("OS_IMAGE_NAME"), }, "flavor_id": &schema.Schema{ @@ -353,6 +355,18 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err } d.Set("flavor_name", flavor.Name) + imageId, ok := server.Image["id"].(string) + if !ok { + return fmt.Errorf("Error setting OpenStack server's image: %v", server.Image) + } + d.Set("image_id", imageId) + + image, err := images.Get(computeClient, imageId).Extract() + if err != nil { + return err + } + d.Set("image_name", image.Name) + return nil } From 16ea14e8c914f15340d947f34658f40b023f1896 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Wed, 11 Feb 2015 17:08:23 +0000 Subject: [PATCH 237/295] Grammar fix --- .../openstack/resource_openstack_compute_instance_v2.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index fbc7a7dd7..0025e7f8a 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -781,5 +781,5 @@ func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (str return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, flavorName) } } - return "", fmt.Errorf("Neither an flavor ID nor an flavor name were able to be determined.") + return "", fmt.Errorf("Neither a flavor ID nor a flavor name were able to be determined.") } From e5f2315bfeaab93680e96e824932cd9c346eb558 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Tue, 10 Feb 2015 16:01:08 +0000 Subject: [PATCH 238/295] Instance volume attach This commit adds the ability for instances to attach volumes from within their resource. --- .../resource_openstack_compute_instance_v2.go | 186 ++++++++++++++++++ ...urce_openstack_compute_instance_v2_test.go | 69 +++++++ 2 files changed, 255 insertions(+) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 0025e7f8a..1dc0a1616 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -1,6 +1,7 @@ package openstack import ( + "bytes" "fmt" "log" "time" @@ -13,6 +14,7 @@ import ( "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" "github.com/rackspace/gophercloud/openstack/compute/v2/flavors" "github.com/rackspace/gophercloud/openstack/compute/v2/images" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" @@ -170,6 +172,30 @@ func resourceComputeInstanceV2() *schema.Resource { }, }, }, + "volume": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "volume_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "device": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + }, + Set: resourceComputeVolumeAttachmentHash, + }, }, } } @@ -268,6 +294,20 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e } } + // were volume attachments specified? + if v := d.Get("volume"); v != nil { + vols := v.(*schema.Set).List() + if len(vols) > 0 { + if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { + return fmt.Errorf("Error creating OpenStack block storage client: %s", err) + } else { + if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), vols); err != nil { + return err + } + } + } + } + return resourceComputeInstanceV2Read(d, meta) } @@ -367,6 +407,23 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err } d.Set("image_name", image.Name) + // volume attachments + vas, err := getVolumeAttachments(computeClient, d.Id()) + if err != nil { + return err + } + if len(vas) > 0 { + attachments := make([]map[string]interface{}, len(vas)) + for i, attachment := range vas { + attachments[i] = make(map[string]interface{}) + attachments[i]["id"] = attachment.ID + attachments[i]["volume_id"] = attachment.VolumeID + attachments[i]["device"] = attachment.Device + } + log.Printf("[INFO] Volume attachments: %v", attachments) + d.Set("volume", attachments) + } + return nil } @@ -464,6 +521,37 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e } } + if d.HasChange("volume") { + // old attachments and new attachments + oldAttachments, newAttachments := d.GetChange("volume") + + // for each old attachment, detach the volume + oldAttachmentSet := oldAttachments.(*schema.Set).List() + if len(oldAttachmentSet) > 0 { + if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { + return err + } else { + if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), oldAttachmentSet); err != nil { + return err + } + } + } + + // for each new attachment, attach the volume + newAttachmentSet := newAttachments.(*schema.Set).List() + if len(newAttachmentSet) > 0 { + if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { + return err + } else { + if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), newAttachmentSet); err != nil { + return err + } + } + } + + d.SetPartial("volume") + } + if d.HasChange("flavor_ref") { resizeOpts := &servers.ResizeOpts{ FlavorRef: d.Get("flavor_ref").(string), @@ -783,3 +871,101 @@ func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (str } return "", fmt.Errorf("Neither a flavor ID nor a flavor name were able to be determined.") } + +func resourceComputeVolumeAttachmentHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["device"].(string))) + return hashcode.String(buf.String()) +} + +func attachVolumesToInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { + if len(vols) > 0 { + for _, v := range vols { + va := v.(map[string]interface{}) + volumeId := va["volume_id"].(string) + device := va["device"].(string) + + s := "" + if serverId != "" { + s = serverId + } else if va["server_id"] != "" { + s = va["server_id"].(string) + } else { + return fmt.Errorf("Unable to determine server ID to attach volume.") + } + + vaOpts := &volumeattach.CreateOpts{ + Device: device, + VolumeID: volumeId, + } + + if _, err := volumeattach.Create(computeClient, s, vaOpts).Extract(); err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Target: "in-use", + Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), + Timeout: 30 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 2 * time.Second, + } + + if _, err := stateConf.WaitForState(); err != nil { + return err + } + + log.Printf("[INFO] Attached volume %s to instance %s", volumeId, serverId) + } + } + return nil +} + +func detachVolumesFromInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { + if len(vols) > 0 { + for _, v := range vols { + va := v.(map[string]interface{}) + aId := va["id"].(string) + + if err := volumeattach.Delete(computeClient, serverId, aId).ExtractErr(); err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Target: "available", + Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), + Timeout: 30 * time.Minute, + Delay: 5 * time.Second, + MinTimeout: 2 * time.Second, + } + + if _, err := stateConf.WaitForState(); err != nil { + return err + } + log.Printf("[INFO] Detached volume %s from instance %s", va["volume_id"], serverId) + } + } + + return nil +} + +func getVolumeAttachments(computeClient *gophercloud.ServiceClient, serverId string) ([]volumeattach.VolumeAttachment, error) { + var attachments []volumeattach.VolumeAttachment + err := volumeattach.List(computeClient, serverId).EachPage(func(page pagination.Page) (bool, error) { + actual, err := volumeattach.ExtractVolumeAttachments(page) + if err != nil { + return false, err + } + + attachments = actual + return true, nil + }) + + if err != nil { + return nil, err + } + + return attachments, nil +} diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go index 77559fa3a..f4c6c8557 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2_test.go @@ -7,7 +7,10 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" + "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" + "github.com/rackspace/gophercloud/pagination" ) func TestAccComputeV2Instance_basic(t *testing.T) { @@ -29,6 +32,27 @@ func TestAccComputeV2Instance_basic(t *testing.T) { }) } +func TestAccComputeV2Instance_volumeAttach(t *testing.T) { + var instance servers.Server + var volume volumes.Volume + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeV2InstanceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeV2Instance_volumeAttach, + Check: resource.ComposeTestCheckFunc( + testAccCheckBlockStorageV1VolumeExists(t, "openstack_blockstorage_volume_v1.myvol", &volume), + testAccCheckComputeV2InstanceExists(t, "openstack_compute_instance_v2.foo", &instance), + testAccCheckComputeV2InstanceVolumeAttachment(&instance, &volume), + ), + }, + }, + }) +} + func testAccCheckComputeV2InstanceDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) computeClient, err := config.computeV2Client(OS_REGION_NAME) @@ -105,6 +129,36 @@ func testAccCheckComputeV2InstanceMetadata( } } +func testAccCheckComputeV2InstanceVolumeAttachment( + instance *servers.Server, volume *volumes.Volume) resource.TestCheckFunc { + return func(s *terraform.State) error { + var attachments []volumeattach.VolumeAttachment + + config := testAccProvider.Meta().(*Config) + computeClient, err := config.computeV2Client(OS_REGION_NAME) + if err != nil { + return err + } + err = volumeattach.List(computeClient, instance.ID).EachPage(func(page pagination.Page) (bool, error) { + actual, err := volumeattach.ExtractVolumeAttachments(page) + if err != nil { + return false, fmt.Errorf("Unable to lookup attachment: %s", err) + } + + attachments = actual + return true, nil + }) + + for _, attachment := range attachments { + if attachment.VolumeID == volume.ID { + return nil + } + } + + return fmt.Errorf("Volume not found: %s", volume.ID) + } +} + var testAccComputeV2Instance_basic = fmt.Sprintf(` resource "openstack_compute_instance_v2" "foo" { region = "%s" @@ -114,3 +168,18 @@ var testAccComputeV2Instance_basic = fmt.Sprintf(` } }`, OS_REGION_NAME) + +var testAccComputeV2Instance_volumeAttach = fmt.Sprintf(` + resource "openstack_blockstorage_volume_v1" "myvol" { + name = "myvol" + size = 1 + } + + resource "openstack_compute_instance_v2" "foo" { + region = "%s" + name = "terraform-test" + volume { + volume_id = "${openstack_blockstorage_volume_v1.myvol.id}" + } + }`, + OS_REGION_NAME) From 2b152f38b2f72434e499306ce4c659e879f7a950 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Thu, 12 Feb 2015 03:26:22 +0000 Subject: [PATCH 239/295] Doc update for instance volume attachment. --- .../openstack/r/compute_instance_v2.html.markdown | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown index ba03ee01c..36805ed0d 100644 --- a/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_instance_v2.html.markdown @@ -71,6 +71,9 @@ The following arguments are supported: * `block_device` - (Optional) The object for booting by volume. The block_device object structure is documented below. Changing this creates a new server. +* `volume` - (Optional) Attach an existing volume to the instance. The volume + structure is described below. + The `network` block supports: * `uuid` - (Required unless `port` is provided) The network UUID to attach to @@ -96,6 +99,14 @@ The `block_device` block supports: * `destination_type` - (Optional) The type that gets created. Possible values are "volume" and "local". +The `volume` block supports: + +* `volume_id` - (Required) The UUID of the volume to attach. + +* `device` - (Optional) The device that the volume will be attached as. For + example: `/dev/vdc`. Omit this option to allow the volume to be + auto-assigned a device. + ## Attributes Reference The following attributes are exported: From 49b01a4f0a219fab6109e77446cc7a2d29fb4960 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 11 Feb 2015 21:56:26 -0700 Subject: [PATCH 240/295] update compute instance security group Read operation --- .../openstack/resource_openstack_compute_instance_v2.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 1dc0a1616..6406600ae 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -175,7 +175,6 @@ func resourceComputeInstanceV2() *schema.Resource { "volume": &schema.Schema{ Type: schema.TypeSet, Optional: true, - Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "id": &schema.Schema{ @@ -369,19 +368,18 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err d.Set("metadata", server.Metadata) - secGrpNum := 0 + secGrpNames := []string{} err = secgroups.ListByServer(computeClient, d.Id()).EachPage(func(page pagination.Page) (bool, error) { secGrpList, err := secgroups.ExtractSecurityGroups(page) if err != nil { return false, fmt.Errorf("Error getting security groups for OpenStack server: %s", err) } for _, sg := range secGrpList { - d.Set(fmt.Sprintf("security_groups.%d", secGrpNum), sg.Name) - secGrpNum++ + secGrpNames = append(secGrpNames, sg.Name) } return true, nil }) - d.Set("security_groups.#", secGrpNum) + d.Set("security_groups", secGrpNames) flavorId, ok := server.Flavor["id"].(string) if !ok { From bb6969a4c537bd5443ba71b245590d91549c2526 Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 11 Feb 2015 22:05:38 -0700 Subject: [PATCH 241/295] resize server on flavor_id or flavor_name change --- .../resource_openstack_compute_instance_v2.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 6406600ae..6da455ad1 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -550,11 +550,15 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e d.SetPartial("volume") } - if d.HasChange("flavor_ref") { - resizeOpts := &servers.ResizeOpts{ - FlavorRef: d.Get("flavor_ref").(string), + if d.HasChange("flavor_id") || d.HasChange("flavor_name") { + flavorId, err := getFlavorID(computeClient, d) + if err != nil { + return err } - err := servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr() + resizeOpts := &servers.ResizeOpts{ + FlavorRef: flavorId, + } + err = servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr() if err != nil { return fmt.Errorf("Error resizing OpenStack server: %s", err) } From 64d53009a05af30a0a62123811e15686b7fbaddf Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Wed, 11 Feb 2015 22:29:31 -0700 Subject: [PATCH 242/295] typeset->typelist --- .../resource_openstack_compute_instance_v2.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 6da455ad1..ce6d7772f 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -77,13 +77,10 @@ func resourceComputeInstanceV2() *schema.Resource { ForceNew: false, }, "security_groups": &schema.Schema{ - Type: schema.TypeSet, + Type: schema.TypeList, Optional: true, ForceNew: false, Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, }, "availability_zone": &schema.Schema{ Type: schema.TypeString, @@ -374,6 +371,7 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err if err != nil { return false, fmt.Errorf("Error getting security groups for OpenStack server: %s", err) } + log.Printf("[DEBUG] secGrpList: %+v\n\n", secGrpList) for _, sg := range secGrpList { secGrpNames = append(secGrpNames, sg.Name) } @@ -466,7 +464,9 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e if d.HasChange("security_groups") { oldSGRaw, newSGRaw := d.GetChange("security_groups") - oldSGSet, newSGSet := oldSGRaw.(*schema.Set), newSGRaw.(*schema.Set) + oldSGSlice, newSGSlice := oldSGRaw.([]interface{}), newSGRaw.([]interface{}) + oldSGSet := schema.NewSet(func(v interface{}) int { return hashcode.String(v.(string)) }, oldSGSlice) + newSGSet := schema.NewSet(func(v interface{}) int { return hashcode.String(v.(string)) }, newSGSlice) secgroupsToAdd := newSGSet.Difference(oldSGSet) secgroupsToRemove := oldSGSet.Difference(newSGSet) @@ -661,9 +661,9 @@ func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID stri } func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string { - rawSecGroups := d.Get("security_groups").(*schema.Set) - secgroups := make([]string, rawSecGroups.Len()) - for i, raw := range rawSecGroups.List() { + rawSecGroups := d.Get("security_groups").([]interface{}) + secgroups := make([]string, len(rawSecGroups)) + for i, raw := range rawSecGroups { secgroups[i] = raw.(string) } return secgroups From 4df32aebed07d3d222d90a5fdf6f9a12efe641ab Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Thu, 12 Feb 2015 20:58:12 +0000 Subject: [PATCH 243/295] Changing how security groups are read for compute instances --- .../resource_openstack_compute_instance_v2.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index ce6d7772f..9693fb890 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -366,17 +366,9 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err d.Set("metadata", server.Metadata) secGrpNames := []string{} - err = secgroups.ListByServer(computeClient, d.Id()).EachPage(func(page pagination.Page) (bool, error) { - secGrpList, err := secgroups.ExtractSecurityGroups(page) - if err != nil { - return false, fmt.Errorf("Error getting security groups for OpenStack server: %s", err) - } - log.Printf("[DEBUG] secGrpList: %+v\n\n", secGrpList) - for _, sg := range secGrpList { - secGrpNames = append(secGrpNames, sg.Name) - } - return true, nil - }) + for _, sg := range server.SecurityGroups { + secGrpNames = append(secGrpNames, sg["name"].(string)) + } d.Set("security_groups", secGrpNames) flavorId, ok := server.Flavor["id"].(string) From 633e98dffe249a93e9592f46a1ca0b8a5f8a69ae Mon Sep 17 00:00:00 2001 From: Jon Perritt Date: Thu, 12 Feb 2015 17:25:45 -0700 Subject: [PATCH 244/295] security group rule fix; still not exporting rule ID --- .../resource_openstack_compute_instance_v2.go | 13 ++- .../resource_openstack_compute_secgroup_v2.go | 104 ++++++++++++------ builtin/providers/openstack/util.go | 6 +- 3 files changed, 87 insertions(+), 36 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 9693fb890..5462d566b 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -477,9 +477,18 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e for _, g := range secgroupsToRemove.List() { err := secgroups.RemoveServerFromGroup(computeClient, d.Id(), g.(string)).ExtractErr() if err != nil { - return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) + errCode, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok { + return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) + } + if errCode.Actual == 404 { + continue + } else { + return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) + } + } else { + log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g.(string), d.Id()) } - log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g.(string), d.Id()) } } diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index cb61e7458..948bfa80b 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" + "github.com/racker/perigee" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" ) @@ -35,38 +36,41 @@ func resourceComputeSecGroupV2() *schema.Resource { ForceNew: false, }, "rule": &schema.Schema{ - Type: schema.TypeSet, + Type: schema.TypeList, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, "from_port": &schema.Schema{ Type: schema.TypeInt, Required: true, - ForceNew: true, + ForceNew: false, }, "to_port": &schema.Schema{ Type: schema.TypeInt, Required: true, - ForceNew: true, + ForceNew: false, }, "ip_protocol": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: true, + ForceNew: false, }, "cidr": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, + ForceNew: false, }, "from_group_id": &schema.Schema{ Type: schema.TypeString, Optional: true, - ForceNew: true, + ForceNew: false, }, }, }, - Set: resourceSecGroupRuleV2Hash, }, }, } @@ -117,7 +121,8 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err d.Set("region", d.Get("region").(string)) d.Set("name", sg.Name) d.Set("description", sg.Description) - d.Set("rule", sg.Rules) + log.Printf("[DEBUG] rulesToMap(sg.Rules): %+v", rulesToMap(sg.Rules)) + d.Set("rules", rulesToMap(sg.Rules)) return nil } @@ -143,16 +148,18 @@ func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) e if d.HasChange("rule") { oldSGRaw, newSGRaw := d.GetChange("rule") - oldSGRSet, newSGRSet := oldSGRaw.(*schema.Set), newSGRaw.(*schema.Set) + oldSGRSlice, newSGRSlice := oldSGRaw.([]interface{}), newSGRaw.([]interface{}) + oldSGRSet := schema.NewSet(secgroupRuleV2Hash, oldSGRSlice) + newSGRSet := schema.NewSet(secgroupRuleV2Hash, newSGRSlice) secgrouprulesToAdd := newSGRSet.Difference(oldSGRSet) secgrouprulesToRemove := oldSGRSet.Difference(newSGRSet) log.Printf("[DEBUG] Security group rules to add: %v", secgrouprulesToAdd) - log.Printf("[DEBUG] Security groups to remove: %v", secgrouprulesToRemove) + log.Printf("[DEBUG] Security groups rules to remove: %v", secgrouprulesToRemove) for _, rawRule := range secgrouprulesToAdd.List() { - createRuleOpts := resourceSecGroupRuleV2(d, rawRule) + createRuleOpts := resourceSecGroupRuleCreateOptsV2(d, rawRule) rule, err := secgroups.CreateRule(computeClient, createRuleOpts).Extract() if err != nil { return fmt.Errorf("Error adding rule to OpenStack security group (%s): %s", d.Id(), err) @@ -161,12 +168,21 @@ func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) e } for _, r := range secgrouprulesToRemove.List() { - rule := r.(secgroups.Rule) - err := secgroups.DeleteRule(computeClient, "").ExtractErr() + rule := resourceSecGroupRuleV2(d, r) + err := secgroups.DeleteRule(computeClient, rule.ID).ExtractErr() if err != nil { - return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err) + errCode, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok { + return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err) + } + if errCode.Actual == 404 { + continue + } else { + return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s)", rule.ID, d.Id()) + } + } else { + log.Printf("[DEBUG] Removed rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err) } - log.Printf("[DEBUG] Removed rule (%s) from OpenStack security group (%s)", rule.ID, d.Id()) } } @@ -188,22 +204,10 @@ func resourceComputeSecGroupV2Delete(d *schema.ResourceData, meta interface{}) e return nil } -func resourceSecGroupRuleV2Hash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) - buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) - buf.WriteString(fmt.Sprintf("%s-", m["ip_protocol"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["cidr"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["from_group_id"].(string))) - - return hashcode.String(buf.String()) -} - func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts { - rawRules := (d.Get("rule")).(*schema.Set) - createRuleOptsList := make([]secgroups.CreateRuleOpts, rawRules.Len()) - for i, raw := range rawRules.List() { + rawRules := (d.Get("rule")).([]interface{}) + createRuleOptsList := make([]secgroups.CreateRuleOpts, len(rawRules)) + for i, raw := range rawRules { rawMap := raw.(map[string]interface{}) createRuleOptsList[i] = secgroups.CreateRuleOpts{ ParentGroupID: d.Id(), @@ -217,7 +221,7 @@ func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts return createRuleOptsList } -func resourceSecGroupRuleV2(d *schema.ResourceData, raw interface{}) secgroups.CreateRuleOpts { +func resourceSecGroupRuleCreateOptsV2(d *schema.ResourceData, raw interface{}) secgroups.CreateRuleOpts { rawMap := raw.(map[string]interface{}) return secgroups.CreateRuleOpts{ ParentGroupID: d.Id(), @@ -228,3 +232,41 @@ func resourceSecGroupRuleV2(d *schema.ResourceData, raw interface{}) secgroups.C FromGroupID: rawMap["from_group_id"].(string), } } + +func resourceSecGroupRuleV2(d *schema.ResourceData, raw interface{}) secgroups.Rule { + rawMap := raw.(map[string]interface{}) + return secgroups.Rule{ + ID: rawMap["id"].(string), + ParentGroupID: d.Id(), + FromPort: rawMap["from_port"].(int), + ToPort: rawMap["to_port"].(int), + IPProtocol: rawMap["ip_protocol"].(string), + IPRange: secgroups.IPRange{CIDR: rawMap["cidr"].(string)}, + } +} + +func rulesToMap(sgrs []secgroups.Rule) []map[string]interface{} { + sgrMap := make([]map[string]interface{}, len(sgrs)) + for i, sgr := range sgrs { + sgrMap[i] = map[string]interface{}{ + "to_port": sgr.ToPort, + "from_port": sgr.FromPort, + "id": sgr.ID, + "ruleID": sgr.ID, + "cidr": sgr.IPRange.CIDR, + "ip_protocol": sgr.IPProtocol, + } + } + return sgrMap +} + +func secgroupRuleV2Hash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) + buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) + buf.WriteString(fmt.Sprintf("%s-", m["ip_protocol"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["cidr"].(string))) + + return hashcode.String(buf.String()) +} diff --git a/builtin/providers/openstack/util.go b/builtin/providers/openstack/util.go index 7ed9f8f5f..c2a385156 100644 --- a/builtin/providers/openstack/util.go +++ b/builtin/providers/openstack/util.go @@ -9,14 +9,14 @@ import ( // CheckDeleted checks the error to see if it's a 404 (Not Found) and, if so, // sets the resource ID to the empty string instead of throwing an error. -func CheckDeleted(d *schema.ResourceData, err error, resource string) error { +func CheckDeleted(d *schema.ResourceData, err error, msg string) error { errCode, ok := err.(*perigee.UnexpectedResponseCodeError) if !ok { - return fmt.Errorf("Error retrieving OpenStack %s: %s", resource, err) + return fmt.Errorf("%s: %s", msg, err) } if errCode.Actual == 404 { d.SetId("") return nil } - return fmt.Errorf("Error retrieving OpenStack %s: %s", resource, err) + return fmt.Errorf("%s: %s", msg, err) } From 4c9a44b69f292381a0b231828358d5c4ea7364a7 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Tue, 10 Feb 2015 05:47:35 +0000 Subject: [PATCH 245/295] Added access_ip_v6 support This commit populates access_ip_v6 by either the AccessIPv6 attribute or by finding the first available IPv6 address. This commit retains the original feature of setting the default ssh connection to the IPv4 address unless one is not found. IPv6 access can still be enabled by explicitly setting it in the resource paramters. This commit also removes d.Set("host") in favor of SetConnInfo --- .../resource_openstack_compute_instance_v2.go | 64 +++++++++++++++---- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 5462d566b..f7d92b14f 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -326,42 +326,78 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err d.Set("access_ip_v4", server.AccessIPv4) d.Set("access_ip_v6", server.AccessIPv6) - host := server.AccessIPv4 - if host == "" { + hostv4 := server.AccessIPv4 + if hostv4 == "" { if publicAddressesRaw, ok := server.Addresses["public"]; ok { publicAddresses := publicAddressesRaw.([]interface{}) for _, paRaw := range publicAddresses { pa := paRaw.(map[string]interface{}) if pa["version"].(float64) == 4 { - host = pa["addr"].(string) + hostv4 = pa["addr"].(string) break } } } } - // If no host found, just get the first IP we find - if host == "" { + // If no host found, just get the first IPv4 we find + if hostv4 == "" { for _, networkAddresses := range server.Addresses { for _, element := range networkAddresses.([]interface{}) { address := element.(map[string]interface{}) if address["version"].(float64) == 4 { - host = address["addr"].(string) + hostv4 = address["addr"].(string) break } } } } - d.Set("access_ip_v4", host) - d.Set("host", host) + d.Set("access_ip_v4", hostv4) + log.Printf("hostv4: %s", hostv4) - log.Printf("host: %s", host) + hostv6 := server.AccessIPv6 + if hostv6 == "" { + if publicAddressesRaw, ok := server.Addresses["public"]; ok { + publicAddresses := publicAddressesRaw.([]interface{}) + for _, paRaw := range publicAddresses { + pa := paRaw.(map[string]interface{}) + if pa["version"].(float64) == 4 { + hostv6 = fmt.Sprintf("[%s]", pa["addr"].(string)) + break + } + } + } + } - // Initialize the connection info - d.SetConnInfo(map[string]string{ - "type": "ssh", - "host": host, - }) + // If no hostv6 found, just get the first IPv6 we find + if hostv6 == "" { + for _, networkAddresses := range server.Addresses { + for _, element := range networkAddresses.([]interface{}) { + address := element.(map[string]interface{}) + if address["version"].(float64) == 6 { + hostv6 = fmt.Sprintf("[%s]", address["addr"].(string)) + break + } + } + } + } + d.Set("access_ip_v6", hostv6) + log.Printf("hostv6: %s", hostv6) + + preferredv := "" + if hostv4 != "" { + preferredv = hostv4 + } else if hostv6 != "" { + preferredv = hostv6 + } + + if preferredv != "" { + // Initialize the connection info + d.SetConnInfo(map[string]string{ + "type": "ssh", + "host": preferredv, + }) + } d.Set("metadata", server.Metadata) From 79e5c419c3b9ffac4b328966741afb9ce5af4195 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Fri, 13 Feb 2015 05:26:35 +0000 Subject: [PATCH 246/295] Fixing rule/rules and re-arranged order for schema consistency --- .../openstack/resource_openstack_compute_secgroup_v2.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index 948bfa80b..f1ee715a2 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -122,7 +122,7 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err d.Set("name", sg.Name) d.Set("description", sg.Description) log.Printf("[DEBUG] rulesToMap(sg.Rules): %+v", rulesToMap(sg.Rules)) - d.Set("rules", rulesToMap(sg.Rules)) + d.Set("rule", rulesToMap(sg.Rules)) return nil } @@ -249,12 +249,11 @@ func rulesToMap(sgrs []secgroups.Rule) []map[string]interface{} { sgrMap := make([]map[string]interface{}, len(sgrs)) for i, sgr := range sgrs { sgrMap[i] = map[string]interface{}{ - "to_port": sgr.ToPort, - "from_port": sgr.FromPort, "id": sgr.ID, - "ruleID": sgr.ID, - "cidr": sgr.IPRange.CIDR, + "from_port": sgr.FromPort, + "to_port": sgr.ToPort, "ip_protocol": sgr.IPProtocol, + "cidr": sgr.IPRange.CIDR, } } return sgrMap From 42fb14f19a85cc0a6b340498ccb889d52672306f Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Mon, 16 Feb 2015 16:04:08 -0500 Subject: [PATCH 247/295] Added self option to security groups --- .../resource_openstack_compute_secgroup_v2.go | 17 +++++++++++++++-- .../r/compute_secgroup_v2.html.markdown | 5 ++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index f1ee715a2..013040db8 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -69,6 +69,11 @@ func resourceComputeSecGroupV2() *schema.Resource { Optional: true, ForceNew: false, }, + "self": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: false, + }, }, }, }, @@ -209,13 +214,17 @@ func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts createRuleOptsList := make([]secgroups.CreateRuleOpts, len(rawRules)) for i, raw := range rawRules { rawMap := raw.(map[string]interface{}) + groupId := rawMap["from_group_id"].(string) + if rawMap["self"].(bool) { + groupId = d.Id() + } createRuleOptsList[i] = secgroups.CreateRuleOpts{ ParentGroupID: d.Id(), FromPort: rawMap["from_port"].(int), ToPort: rawMap["to_port"].(int), IPProtocol: rawMap["ip_protocol"].(string), CIDR: rawMap["cidr"].(string), - FromGroupID: rawMap["from_group_id"].(string), + FromGroupID: groupId, } } return createRuleOptsList @@ -223,13 +232,17 @@ func resourceSecGroupRulesV2(d *schema.ResourceData) []secgroups.CreateRuleOpts func resourceSecGroupRuleCreateOptsV2(d *schema.ResourceData, raw interface{}) secgroups.CreateRuleOpts { rawMap := raw.(map[string]interface{}) + groupId := rawMap["from_group_id"].(string) + if rawMap["self"].(bool) { + groupId = d.Id() + } return secgroups.CreateRuleOpts{ ParentGroupID: d.Id(), FromPort: rawMap["from_port"].(int), ToPort: rawMap["to_port"].(int), IPProtocol: rawMap["ip_protocol"].(string), CIDR: rawMap["cidr"].(string), - FromGroupID: rawMap["from_group_id"].(string), + FromGroupID: groupId, } } diff --git a/website/source/docs/providers/openstack/r/compute_secgroup_v2.html.markdown b/website/source/docs/providers/openstack/r/compute_secgroup_v2.html.markdown index 50f328502..5b9538793 100644 --- a/website/source/docs/providers/openstack/r/compute_secgroup_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/compute_secgroup_v2.html.markdown @@ -59,10 +59,13 @@ this creates a new security group rule. will be the source of network traffic to the security group. Use 0.0.0.0./0 to allow all IP addresses. Changing this creates a new security group rule. -* `from_group_id - (Optional) Required if `cidr` is empty. The ID of a group +* `from_group_id` - (Optional) Required if `cidr` is empty. The ID of a group from which to forward traffic to the parent group. Changing this creates a new security group rule. +* `self` - (Optional) Required if `cidr` and `from_group_id` is empty. If true, +the security group itself will be added as a source to this ingress rule. + ## Attributes Reference The following attributes are exported: From d03b420e623c4a9c4e8992c3f3d3ba5d573d2e9f Mon Sep 17 00:00:00 2001 From: Eric Bellemon Date: Tue, 17 Feb 2015 18:48:23 +0100 Subject: [PATCH 248/295] Replace perigee.UnexpectedResponseCodeError with gophercloud.UnexpectedResponseCodeError --- .../openstack/resource_openstack_blockstorage_volume_v1.go | 3 +-- .../openstack/resource_openstack_compute_instance_v2.go | 5 ++--- .../openstack/resource_openstack_compute_secgroup_v2.go | 4 ++-- .../resource_openstack_networking_router_interface_v2.go | 4 ++-- .../openstack/resource_openstack_networking_router_v2.go | 4 ++-- builtin/providers/openstack/util.go | 4 ++-- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go index ab85317bb..f38fea5fb 100644 --- a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go +++ b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" ) @@ -252,7 +251,7 @@ func VolumeV1StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string return func() (interface{}, string, error) { v, err := volumes.Get(client, volumeID).Extract() if err != nil { - errCode, ok := err.(*perigee.UnexpectedResponseCodeError) + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok { return nil, "", err } diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index f7d92b14f..348806931 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" @@ -513,7 +512,7 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e for _, g := range secgroupsToRemove.List() { err := secgroups.RemoveServerFromGroup(computeClient, d.Id(), g.(string)).ExtractErr() if err != nil { - errCode, ok := err.(*perigee.UnexpectedResponseCodeError) + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok { return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) } @@ -683,7 +682,7 @@ func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID stri return func() (interface{}, string, error) { s, err := servers.Get(client, instanceID).Extract() if err != nil { - errCode, ok := err.(*perigee.UnexpectedResponseCodeError) + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok { return nil, "", err } diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index 013040db8..cf00f7f1b 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" - "github.com/racker/perigee" + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" ) @@ -176,7 +176,7 @@ func resourceComputeSecGroupV2Update(d *schema.ResourceData, meta interface{}) e rule := resourceSecGroupRuleV2(d, r) err := secgroups.DeleteRule(computeClient, rule.ID).ExtractErr() if err != nil { - errCode, ok := err.(*perigee.UnexpectedResponseCodeError) + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok { return fmt.Errorf("Error removing rule (%s) from OpenStack security group (%s): %s", rule.ID, d.Id(), err) } diff --git a/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go index 2daaf787d..492c14d30 100644 --- a/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go @@ -5,7 +5,7 @@ import ( "log" "github.com/hashicorp/terraform/helper/schema" - "github.com/racker/perigee" + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" "github.com/rackspace/gophercloud/openstack/networking/v2/ports" ) @@ -69,7 +69,7 @@ func resourceNetworkingRouterInterfaceV2Read(d *schema.ResourceData, meta interf n, err := ports.Get(networkingClient, d.Id()).Extract() if err != nil { - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok { return fmt.Errorf("Error retrieving OpenStack Neutron Router Interface: %s", err) } diff --git a/builtin/providers/openstack/resource_openstack_networking_router_v2.go b/builtin/providers/openstack/resource_openstack_networking_router_v2.go index c4fe8e90a..cb235a88b 100644 --- a/builtin/providers/openstack/resource_openstack_networking_router_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_router_v2.go @@ -6,7 +6,7 @@ import ( "strconv" "github.com/hashicorp/terraform/helper/schema" - "github.com/racker/perigee" + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/routers" ) @@ -98,7 +98,7 @@ func resourceNetworkingRouterV2Read(d *schema.ResourceData, meta interface{}) er n, err := routers.Get(networkingClient, d.Id()).Extract() if err != nil { - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok { return fmt.Errorf("Error retrieving OpenStack Neutron Router: %s", err) } diff --git a/builtin/providers/openstack/util.go b/builtin/providers/openstack/util.go index c2a385156..93a8bfbc5 100644 --- a/builtin/providers/openstack/util.go +++ b/builtin/providers/openstack/util.go @@ -4,13 +4,13 @@ import ( "fmt" "github.com/hashicorp/terraform/helper/schema" - "github.com/racker/perigee" + "github.com/rackspace/gophercloud" ) // CheckDeleted checks the error to see if it's a 404 (Not Found) and, if so, // sets the resource ID to the empty string instead of throwing an error. func CheckDeleted(d *schema.ResourceData, err error, msg string) error { - errCode, ok := err.(*perigee.UnexpectedResponseCodeError) + errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok { return fmt.Errorf("%s: %s", msg, err) } From c3c4840bafc735a75d343ffaa87f5bb5d96eba40 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Fri, 13 Feb 2015 20:59:41 +0000 Subject: [PATCH 249/295] openstack_compute_floatingip_v2 This commit adds a resource that allows the user to allocate, deallocate, associate, and disassociate floating IPs through the nova api. --- builtin/providers/openstack/provider.go | 1 + ...esource_openstack_compute_floatingip_v2.go | 121 ++++++++++++++++++ ...ce_openstack_compute_floatingip_v2_test.go | 86 +++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go create mode 100644 builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 450e1c705..6a708a28f 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -63,6 +63,7 @@ func Provider() terraform.ResourceProvider { "openstack_compute_instance_v2": resourceComputeInstanceV2(), "openstack_compute_keypair_v2": resourceComputeKeypairV2(), "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), + "openstack_compute_floatingip_v2": resourceComputeFloatingIPV2(), "openstack_lb_monitor_v1": resourceLBMonitorV1(), "openstack_lb_pool_v1": resourceLBPoolV1(), "openstack_lb_vip_v1": resourceLBVipV1(), diff --git a/builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go new file mode 100644 index 000000000..5a78483de --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go @@ -0,0 +1,121 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" +) + +func resourceComputeFloatingIPV2() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeFloatingIPV2Create, + Read: resourceComputeFloatingIPV2Read, + Update: nil, + Delete: resourceComputeFloatingIPV2Delete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, + + "pool": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_POOL_NAME"), + }, + + // exported + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "address": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "fixed_ip": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "instance_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceComputeFloatingIPV2Create(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + createOpts := &floatingip.CreateOpts{ + Pool: d.Get("pool").(string), + } + newFip, err := floatingip.Create(computeClient, createOpts).Extract() + if err != nil { + return fmt.Errorf("Error creating Floating IP: %s", err) + } + + d.SetId(newFip.ID) + + return resourceComputeFloatingIPV2Read(d, meta) +} + +func resourceComputeFloatingIPV2Read(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + fip, err := floatingip.Get(computeClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error getting Floating IP: %s", err) + } + + log.Printf("[DEBUG] Retrieved Floating IP %s: %+v", d.Id(), fip) + + d.Set("id", d.Id()) + d.Set("region", d.Get("region").(string)) + d.Set("pool", fip.Pool) + d.Set("instance_id", fip.InstanceID) + d.Set("address", fip.IP) + d.Set("fixed_ip", fip.FixedIP) + + return nil +} + +func resourceComputeFloatingIPV2Delete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + computeClient, err := config.computeV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack compute client: %s", err) + } + + fip, err := floatingip.Get(computeClient, d.Id()).Extract() + if err != nil { + return fmt.Errorf("Error getting Floating IP for update: %s", err) + } + + log.Printf("[DEBUG] Deleting Floating IP %s", fip.IP) + + // Now do the actual deletion + if err := floatingip.Delete(computeClient, fip.ID).ExtractErr(); err != nil { + return fmt.Errorf("Error deleting Floating IP: %s", err) + } + + return nil +} diff --git a/builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go new file mode 100644 index 000000000..c246d1a51 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go @@ -0,0 +1,86 @@ +package openstack + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip" +) + +func TestAccComputeV2FloatingIP_basic(t *testing.T) { + var floatingIP floatingip.FloatingIP + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeV2FloatingIPDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeV2FloatingIP_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeV2FloatingIPExists(t, "openstack_compute_floatingip_v2.foo", &floatingIP), + ), + }, + }, + }) +} + +func testAccCheckComputeV2FloatingIPDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + computeClient, err := config.computeV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckComputeV2FloatingIPDestroy) Error creating OpenStack compute client: %s", err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_compute_floatingip_v2" { + continue + } + + _, err := floatingip.Get(computeClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("FloatingIP still exists") + } + } + + return nil +} + +func testAccCheckComputeV2FloatingIPExists(t *testing.T, n string, kp *floatingip.FloatingIP) 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") + } + + config := testAccProvider.Meta().(*Config) + computeClient, err := config.computeV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckComputeV2FloatingIPExists) Error creating OpenStack compute client: %s", err) + } + + found, err := floatingip.Get(computeClient, rs.Primary.ID).Extract() + if err != nil { + return err + } + + if found.ID != rs.Primary.ID { + return fmt.Errorf("FloatingIP not found") + } + + *kp = *found + + return nil + } +} + +var testAccComputeV2FloatingIP_basic = fmt.Sprintf(` + resource "openstack_compute_floatingip_v2" "foo" { + }`) From b7091414fe7edf4cbfb725c14474bc5b96e4161b Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Thu, 19 Feb 2015 22:22:37 +0000 Subject: [PATCH 250/295] Volume Safe Delete This commit ensures that a volume is detached from all instances before it is deleted. It also adds in an `attachment` exported parameter that shows details of the volume's attachment(s). --- ...source_openstack_blockstorage_volume_v1.go | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go index f38fea5fb..eefd75f7e 100644 --- a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go +++ b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go @@ -1,14 +1,17 @@ package openstack import ( + "bytes" "fmt" "log" "time" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes" + "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" ) func resourceBlockStorageVolumeV1() *schema.Resource { @@ -65,6 +68,27 @@ func resourceBlockStorageVolumeV1() *schema.Resource { Optional: true, ForceNew: true, }, + "attachment": &schema.Schema{ + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "instance_id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "device": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + }, + Set: resourceVolumeAttachmentHash, + }, }, } } @@ -175,6 +199,18 @@ func resourceBlockStorageVolumeV1Read(d *schema.ResourceData, meta interface{}) d.Set("metadata", "") } + if len(v.Attachments) > 0 { + attachments := make([]map[string]interface{}, len(v.Attachments)) + for i, attachment := range v.Attachments { + attachments[i] = make(map[string]interface{}) + attachments[i]["id"] = attachment["id"] + attachments[i]["instance_id"] = attachment["server_id"] + attachments[i]["device"] = attachment["device"] + log.Printf("[DEBUG] attachment: %v", attachment) + } + d.Set("attachment", attachments) + } + return nil } @@ -209,6 +245,42 @@ func resourceBlockStorageVolumeV1Delete(d *schema.ResourceData, meta interface{} return fmt.Errorf("Error creating OpenStack block storage client: %s", err) } + v, err := volumes.Get(blockStorageClient, d.Id()).Extract() + if err != nil { + return CheckDeleted(d, err, "volume") + } + + // make sure this volume is detached from all instances before deleting + if len(v.Attachments) > 0 { + log.Printf("[DEBUG] detaching volumes") + if computeClient, err := config.computeV2Client(d.Get("region").(string)); err != nil { + return err + } else { + for _, volumeAttachment := range v.Attachments { + log.Printf("[DEBUG] Attachment: %v", volumeAttachment) + if err := volumeattach.Delete(computeClient, volumeAttachment["server_id"].(string), volumeAttachment["id"].(string)).ExtractErr(); err != nil { + return err + } + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"in-use"}, + Target: "available", + Refresh: VolumeV1StateRefreshFunc(blockStorageClient, d.Id()), + Timeout: 10 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf( + "Error waiting for volume (%s) to become available: %s", + d.Id(), err) + } + } + } + err = volumes.Delete(blockStorageClient, d.Id()).ExtractErr() if err != nil { return fmt.Errorf("Error deleting OpenStack volume: %s", err) @@ -264,3 +336,12 @@ func VolumeV1StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string return v, v.Status, nil } } + +func resourceVolumeAttachmentHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + if m["instance_id"] != nil { + buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string))) + } + return hashcode.String(buf.String()) +} From 102848525f4e2a1b31200994b67c4ce409cc8b28 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Fri, 20 Feb 2015 05:08:09 +0000 Subject: [PATCH 251/295] Added CheckDelete to handle bad Gets. Also removed unneeded Get from Delete. --- .../resource_openstack_compute_floatingip_v2.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go index 5a78483de..3501fe282 100644 --- a/builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go @@ -83,7 +83,7 @@ func resourceComputeFloatingIPV2Read(d *schema.ResourceData, meta interface{}) e fip, err := floatingip.Get(computeClient, d.Id()).Extract() if err != nil { - return fmt.Errorf("Error getting Floating IP: %s", err) + return CheckDeleted(d, err, "floating ip") } log.Printf("[DEBUG] Retrieved Floating IP %s: %+v", d.Id(), fip) @@ -105,15 +105,8 @@ func resourceComputeFloatingIPV2Delete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error creating OpenStack compute client: %s", err) } - fip, err := floatingip.Get(computeClient, d.Id()).Extract() - if err != nil { - return fmt.Errorf("Error getting Floating IP for update: %s", err) - } - - log.Printf("[DEBUG] Deleting Floating IP %s", fip.IP) - - // Now do the actual deletion - if err := floatingip.Delete(computeClient, fip.ID).ExtractErr(); err != nil { + log.Printf("[DEBUG] Deleting Floating IP %s", d.Id()) + if err := floatingip.Delete(computeClient, d.Id()).ExtractErr(); err != nil { return fmt.Errorf("Error deleting Floating IP: %s", err) } From 552b0af2016e893919cfa572b20c12496dbea60a Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Mon, 2 Feb 2015 21:36:21 +0100 Subject: [PATCH 252/295] Add FWaaS rule resource --- .../resource_openstack_fw_rule_v2.go | 219 ++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_fw_rule_v2.go diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go new file mode 100644 index 000000000..97d8da568 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go @@ -0,0 +1,219 @@ +package openstack + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/racker/perigee" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules" +) + +func resourceFWRuleV2() *schema.Resource { + return &schema.Resource{ + Create: resourceFirewallRuleCreate, + Read: resourceFirewallRuleRead, + Update: resourceFirewallRuleUpdate, + Delete: resourceFirewallRuleDelete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "action": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "ip_version": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 4, + }, + "source_ip_address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "destination_ip_address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "source_port": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "destination_port": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "enabled": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + } +} + +func resourceFirewallRuleCreate(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + enabled := d.Get("enabled").(bool) + + ruleConfiguration := rules.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + Protocol: d.Get("protocol").(string), + Action: d.Get("action").(string), + IPVersion: d.Get("ip_version").(int), + SourceIPAddress: d.Get("source_ip_address").(string), + DestinationIPAddress: d.Get("destination_ip_address").(string), + SourcePort: d.Get("source_port").(string), + DestinationPort: d.Get("destination_port").(string), + Enabled: &enabled, + } + + log.Printf("[DEBUG] Create firewall rule: %#v", ruleConfiguration) + + rule, err := rules.Create(networkingClient, ruleConfiguration).Extract() + + if err != nil { + return err + } + + log.Printf("[DEBUG] Firewall rule with id %s : %#v", rule.ID, rule) + + d.SetId(rule.ID) + + d.Set("name", rule.Name) + d.Set("description", rule.Description) + d.Set("protocol", rule.Protocol) + d.Set("action", rule.Action) + d.Set("ip_version", rule.IPVersion) + d.Set("source_ip_address", rule.SourceIPAddress) + d.Set("destination_ip_address", rule.DestinationIPAddress) + d.Set("source_port", rule.SourcePort) + d.Set("destination_port", rule.DestinationPort) + d.Set("enabled", rule.Enabled) + + return nil +} + +func resourceFirewallRuleRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Retrieve information about firewall rule: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + rule, err := rules.Get(networkingClient, d.Id()).Extract() + + if err != nil { + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok { + return err + } + if httpError.Actual == 404 { + d.SetId("") + return nil + } + return err + } + + d.Set("name", rule.Name) + d.Set("description", rule.Description) + d.Set("protocol", rule.Protocol) + d.Set("action", rule.Action) + d.Set("ip_version", rule.IPVersion) + d.Set("source_ip_address", rule.SourceIPAddress) + d.Set("destination_ip_address", rule.DestinationIPAddress) + d.Set("source_port", rule.SourcePort) + d.Set("destination_port", rule.DestinationPort) + d.Set("enabled", rule.Enabled) + + return nil +} + +func resourceFirewallRuleUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + opts := rules.UpdateOpts{} + + if d.HasChange("name") { + name := d.Get("name").(string) + opts.Name = &name + } + if d.HasChange("description") { + description := d.Get("description").(string) + opts.Description = &description + } + if d.HasChange("protocol") { + opts.Protocol = d.Get("protocol").(string) + } + if d.HasChange("action") { + opts.Action = d.Get("action").(string) + } + if d.HasChange("ip_version") { + opts.IPVersion = d.Get("ip_version").(int) + } + if d.HasChange("source_ip_address") { + sourceIPAddress := d.Get("source_ip_address").(string) + opts.SourceIPAddress = &sourceIPAddress + } + if d.HasChange("destination_ip_address") { + destinationIPAddress := d.Get("destination_ip_address").(string) + opts.DestinationIPAddress = &destinationIPAddress + } + if d.HasChange("source_port") { + sourcePort := d.Get("source_port").(string) + opts.SourcePort = &sourcePort + } + if d.HasChange("destination_port") { + destinationPort := d.Get("destination_port").(string) + opts.DestinationPort = &destinationPort + } + if d.HasChange("enabled") { + enabled := d.Get("enabled").(bool) + opts.Enabled = &enabled + } + + log.Printf("[DEBUG] Updating firewall rules: %#v", opts) + + return rules.Update(networkingClient, d.Id(), opts).Err +} + +func resourceFirewallRuleDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Destroy firewall rule: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + return rules.Delete(networkingClient, d.Id()).Err +} From f829427151c432acc5aebf66a03264d0b169044b Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Tue, 3 Feb 2015 20:56:13 +0100 Subject: [PATCH 253/295] Add FWaaS policy resource --- .../resource_openstack_fw_policy_v2.go | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_fw_policy_v2.go diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v2.go b/builtin/providers/openstack/resource_openstack_fw_policy_v2.go new file mode 100644 index 000000000..8f7b593d8 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v2.go @@ -0,0 +1,196 @@ +package openstack + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" + "github.com/racker/perigee" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies" +) + +func resourceFWPolicyV2() *schema.Resource { + return &schema.Resource{ + Create: resourceFirewallPolicyCreate, + Read: resourceFirewallPolicyRead, + Update: resourceFirewallPolicyUpdate, + Delete: resourceFirewallPolicyDelete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "audited": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "shared": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "rules": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + }, + } +} + +func resourceFirewallPolicyCreate(d *schema.ResourceData, meta interface{}) error { + + // TODO To remove + time.Sleep(time.Second * 5) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + v := d.Get("rules").(*schema.Set) + + log.Printf("[DEBUG] Rules found : %#v", v) + log.Printf("[DEBUG] Rules count : %d", v.Len()) + + rules := make([]string, v.Len()) + for i, v := range v.List() { + rules[i] = v.(string) + } + + opts := policies.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + // Audited: d.Get("audited").(bool), + // Shared: d.Get("shared").(bool), + Rules: rules, + } + + log.Printf("[DEBUG] Create firewall policy: %#v", opts) + + policy, err := policies.Create(networkingClient, opts).Extract() + if err != nil { + return err + } + + log.Printf("[DEBUG] Firewall policy craeted: %#v", policy) + + d.SetId(policy.ID) + + return nil +} + +func resourceFirewallPolicyRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Retrieve information about firewall policy: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + policy, err := policies.Get(networkingClient, d.Id()).Extract() + + if err != nil { + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok { + return err + } + if httpError.Actual == 404 { + d.SetId("") + return nil + } + return err + } + + d.Set("name", policy.Name) + + return nil +} + +func resourceFirewallPolicyUpdate(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + opts := policies.UpdateOpts{} + + if d.HasChange("name") { + name := d.Get("name").(string) + opts.Name = &name + } + + if d.HasChange("description") { + description := d.Get("description").(string) + opts.Description = &description + } + + if d.HasChange("rules") { + v := d.Get("rules").(*schema.Set) + + log.Printf("[DEBUG] Rules found : %#v", v) + log.Printf("[DEBUG] Rules count : %d", v.Len()) + + rules := make([]string, v.Len()) + for i, v := range v.List() { + rules[i] = v.(string) + } + opts.Rules = rules + } + + log.Printf("[DEBUG] Updating firewall policy with id %s: %#v", d.Id(), opts) + + return policies.Update(networkingClient, d.Id(), opts).Err +} + +func resourceFirewallPolicyDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Destroy firewall policy: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + for i := 0; i < 15; i++ { + + err = policies.Delete(networkingClient, d.Id()).Err + if err == nil { + break + } + + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok || httpError.Actual != 409 { + return err + } + + // This error usualy means that the policy is attached + // to a firewall. At this point, the firewall is probably + // being delete. So, we retry a few times. + + time.Sleep(time.Second * 2) + } + + return err +} From 5d42242d4b541b9abd9b332e5a318abd23ec414c Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Tue, 3 Feb 2015 22:14:56 +0100 Subject: [PATCH 254/295] Add FWaaS firewall resource --- .../resource_openstack_fw_firewall_v2.go | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_fw_firewall_v2.go diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go new file mode 100644 index 000000000..e2125e7fb --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go @@ -0,0 +1,236 @@ +package openstack + +import ( + "errors" + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/racker/perigee" + "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" +) + +func resourceFWFirewallV2() *schema.Resource { + return &schema.Resource{ + Create: resourceFirewallCreate, + Read: resourceFirewallRead, + Update: resourceFirewallUpdate, + Delete: resourceFirewallDelete, + + Schema: map[string]*schema.Schema{ + "region": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_REGION_NAME"), + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "policy_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "admin_state_up": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + } +} + +func resourceFirewallCreate(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + adminStateUp := d.Get("admin_state_up").(bool) + + firewallConfiguration := firewalls.CreateOpts{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + PolicyID: d.Get("policy_id").(string), + AdminStateUp: &adminStateUp, + } + + log.Printf("[DEBUG] Create firewall: %#v", firewallConfiguration) + + firewall, err := firewalls.Create(networkingClient, firewallConfiguration).Extract() + if err != nil { + return err + } + + log.Printf("[DEBUG] Firewall created: %#v", firewall) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_CREATE"}, + Target: "ACTIVE", + Refresh: WaitForFirewallActive(networkingClient, firewall.ID), + Timeout: 30 * time.Second, + Delay: 0, + MinTimeout: 2 * time.Second, + } + + d.SetId(firewall.ID) + + d.Set("name", firewall.Name) + d.Set("description", firewall.Description) + d.Set("policy_id", firewall.PolicyID) + d.Set("admin_state_up", firewall.AdminStateUp) + + _, err = stateConf.WaitForState() + + return nil +} + +func resourceFirewallRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Retrieve information about firewall: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + firewall, err := firewalls.Get(networkingClient, d.Id()).Extract() + if err != nil { + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok { + return err + } + + if httpError.Actual == 404 { + d.SetId("") + return nil + } + return err + } + + d.Set("name", firewall.Name) + d.Set("description", firewall.Description) + d.Set("policy_id", firewall.PolicyID) + d.Set("admin_state_up", firewall.AdminStateUp) + + return nil +} + +func resourceFirewallUpdate(d *schema.ResourceData, meta interface{}) error { + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + opts := firewalls.UpdateOpts{} + + if d.HasChange("name") { + name := d.Get("name").(string) + opts.Name = &name + } + + if d.HasChange("description") { + description := d.Get("description").(string) + opts.Description = &description + } + + if d.HasChange("policy_id") { + opts.PolicyID = d.Get("policy_id").(string) + } + + log.Printf("[DEBUG] Updating firewall with id %s: %#v", d.Id(), opts) + + if err := firewalls.Update(networkingClient, d.Id(), opts).Err; err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_CREATE"}, + Target: "ACTIVE", + Refresh: WaitForFirewallActive(networkingClient, d.Id()), + Timeout: 30 * time.Second, + Delay: 0, + MinTimeout: 2 * time.Second, + } + + _, err = stateConf.WaitForState() + + return err +} + +func resourceFirewallDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Destroy firewall: %s", d.Id()) + + config := meta.(*Config) + networkingClient, err := config.networkingV2Client(d.Get("region").(string)) + if err != nil { + return fmt.Errorf("Error creating OpenStack networking client: %s", err) + } + + err = firewalls.Delete(networkingClient, d.Id()).Err + + if err != nil { + return err + } + + stateConf := &resource.StateChangeConf{ + Pending: []string{"DELETING"}, + Target: "DELETED", + Refresh: WaitForFirewallDeletion(networkingClient, d.Id()), + Timeout: 2 * time.Minute, + Delay: 0, + MinTimeout: 2 * time.Second, + } + + _, err = stateConf.WaitForState() + + return err +} + +func WaitForFirewallActive(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + + return func() (interface{}, string, error) { + fw, err := firewalls.Get(networkingClient, id).Extract() + log.Printf("[DEBUG] Get firewall %s => %#v", id, fw) + + if err != nil { + return nil, "", err + } + return fw, fw.Status, nil + } +} + +func WaitForFirewallDeletion(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { + + return func() (interface{}, string, error) { + fw, err := firewalls.Get(networkingClient, id).Extract() + log.Printf("[DEBUG] Get firewall %s => %#v", id, fw) + + if err != nil { + httpStatus := err.(*perigee.UnexpectedResponseCodeError) + log.Printf("[DEBUG] Get firewall %s status is %d", id, httpStatus.Actual) + + if httpStatus.Actual == 404 { + log.Printf("[DEBUG] Firewall %s is actually deleted", id) + return "", "DELETED", nil + } + return nil, "", errors.New(fmt.Sprintf("Unexpected status code %d", httpStatus.Actual)) + } + + log.Printf("[DEBUG] Firewall %s deletion is pending", id) + return fw, "DELETING", nil + } +} From 3d1001d8fe8864c2b79b83447c2a05fcf4b87e03 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Thu, 5 Feb 2015 13:49:23 +0100 Subject: [PATCH 255/295] Add FWaaS rule acceptance test --- .../resource_openstack_fw_rule_v2_test.go | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go new file mode 100644 index 000000000..86e731b44 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go @@ -0,0 +1,182 @@ +package openstack + +import ( + "fmt" + "reflect" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/racker/perigee" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules" +) + +func TestAccOpenstackFirewallRule(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckOpenstackFirewallRuleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testFirewallRuleMinimalConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckFirewallRuleExists( + "openstack_fw_rule_v2.accept_test_minimal", + &rules.Rule{ + Protocol: "udp", + Action: "deny", + IPVersion: 4, + Enabled: true, + }), + ), + }, + resource.TestStep{ + Config: testFirewallRuleConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckFirewallRuleExists( + "openstack_fw_rule_v2.accept_test", + &rules.Rule{ + Name: "accept_test", + Protocol: "udp", + Action: "deny", + Description: "Terraform accept test", + IPVersion: 4, + SourceIPAddress: "1.2.3.4", + DestinationIPAddress: "4.3.2.0/24", + SourcePort: "444", + DestinationPort: "555", + Enabled: true, + }), + ), + }, + resource.TestStep{ + Config: testFirewallRuleUpdateAllFieldsConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckFirewallRuleExists( + "openstack_fw_rule_v2.accept_test", + &rules.Rule{ + Name: "accept_test_updated_2", + Protocol: "tcp", + Action: "allow", + Description: "Terraform accept test updated", + IPVersion: 4, + SourceIPAddress: "1.2.3.0/24", + DestinationIPAddress: "4.3.2.8", + SourcePort: "666", + DestinationPort: "777", + Enabled: false, + }), + ), + }, + }, + }) +} + +func testAccCheckOpenstackFirewallRuleDestroy(s *terraform.State) error { + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckOpenstackFirewallRuleDestroy) Error creating OpenStack networking client: %s", err) + } + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_firewall_rule" { + continue + } + _, err = rules.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Firewall rule (%s) still exists.", rs.Primary.ID) + } + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok || httpError.Actual != 404 { + return httpError + } + } + return nil +} + +func testAccCheckFirewallRuleExists(n string, expected *rules.Rule) 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") + } + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckFirewallRuleExists) Error creating OpenStack networking client: %s", err) + } + + var found *rules.Rule + for i := 0; i < 5; i++ { + // Firewall rule creation is asynchronous. Retry some times + // if we get a 404 error. Fail on any other error. + found, err = rules.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok || httpError.Actual != 404 { + time.Sleep(time.Second) + continue + } + } + break + } + + if err != nil { + return err + } + + expected.ID = found.ID + + if !reflect.DeepEqual(expected, found) { + return fmt.Errorf("Expected:\n%#v\nFound:\n%#v", expected, found) + } + + return nil + } +} + +const testFirewallRuleMinimalConfig = ` +resource "openstack_fw_rule_v2" "accept_test_minimal" { + protocol = "udp" + action = "deny" +} +` + +const testFirewallRuleConfig = ` +resource "openstack_fw_rule_v2" "accept_test" { + name = "accept_test" + description = "Terraform accept test" + protocol = "udp" + action = "deny" + ip_version = 4 + source_ip_address = "1.2.3.4" + destination_ip_address = "4.3.2.0/24" + source_port = "444" + destination_port = "555" + enabled = true +} +` + +const testFirewallRuleUpdateAllFieldsConfig = ` +resource "openstack_fw_rule_v2" "accept_test" { + name = "accept_test_updated_2" + description = "Terraform accept test updated" + protocol = "tcp" + action = "allow" + ip_version = 4 + source_ip_address = "1.2.3.0/24" + destination_ip_address = "4.3.2.8" + source_port = "666" + destination_port = "777" + enabled = false +} +` From 88a55a5d58c2c6317d0e21de7c25ee481afe5102 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Mon, 9 Feb 2015 22:44:27 +0100 Subject: [PATCH 256/295] Enable FWaaS resources --- builtin/providers/openstack/provider.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 6a708a28f..7e0880007 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -64,6 +64,9 @@ func Provider() terraform.ResourceProvider { "openstack_compute_keypair_v2": resourceComputeKeypairV2(), "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), "openstack_compute_floatingip_v2": resourceComputeFloatingIPV2(), + "openstack_fw_firewall_v2": resourceFWFirewallV2(), + "openstack_fw_policy_v2": resourceFWPolicyV2(), + "openstack_fw_rule_v2": resourceFWRuleV2(), "openstack_lb_monitor_v1": resourceLBMonitorV1(), "openstack_lb_pool_v1": resourceLBPoolV1(), "openstack_lb_vip_v1": resourceLBVipV1(), From 06826fb67742f2635cb9bdec15fbb4ec6763fbb5 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Mon, 9 Feb 2015 22:53:09 +0100 Subject: [PATCH 257/295] Add FWaaS policy acceptance test --- .../resource_openstack_fw_policy_v2_test.go | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go new file mode 100644 index 000000000..8a3f19ef5 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go @@ -0,0 +1,175 @@ +package openstack + +import ( + "fmt" + "reflect" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/racker/perigee" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies" +) + +func TestAccOpenstackFirewallPolicy(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckOpenstackFirewallPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testFirewallPolicyConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckFirewallPolicyExists( + "openstack_fw_policy_v2.accept_test", + &policies.Policy{ + Rules: []string{}, + }), + ), + }, + resource.TestStep{ + Config: testFirewallPolicyConfigAddRules, + Check: resource.ComposeTestCheckFunc( + testAccCheckFirewallPolicyExists( + "openstack_fw_policy_v2.accept_test", + &policies.Policy{ + Name: "accept_test", + Description: "terraform acceptance test", + Rules: []string{ + "", + "", + }, + }), + ), + }, + resource.TestStep{ + Config: testFirewallPolicyUpdateDeleteRule, + Check: resource.ComposeTestCheckFunc( + testAccCheckFirewallPolicyExists( + "openstack_fw_policy_v2.accept_test", + &policies.Policy{}), + ), + }, + }, + }) +} + +func testAccCheckOpenstackFirewallPolicyDestroy(s *terraform.State) error { + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckOpenstackFirewallPolicyDestroy) Error creating OpenStack networking client: %s", err) + } + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_fw_policy_v2" { + continue + } + _, err = policies.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Firewall policy (%s) still exists.", rs.Primary.ID) + } + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok || httpError.Actual != 404 { + return httpError + } + } + return nil +} + +func testAccCheckFirewallPolicyExists(n string, expected *policies.Policy) 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") + } + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckFirewallPolicyExists) Error creating OpenStack networking client: %s", err) + } + + var found *policies.Policy + for i := 0; i < 5; i++ { + // Firewall policy creation is asynchronous. Retry some times + // if we get a 404 error. Fail on any other error. + found, err = policies.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok || httpError.Actual != 404 { + time.Sleep(time.Second) + continue + } + } + break + } + + if err != nil { + return err + } + + expected.ID = found.ID + + if !reflect.DeepEqual(expected, found) { + return fmt.Errorf("Expected:\n%#v\nFound:\n%#v", expected, found) + } + + return nil + } +} + +const testFirewallPolicyConfig = ` +resource "openstack_fw_policy_v2" "accept_test" { + +} +` + +const testFirewallPolicyConfigAddRules = ` +resource "openstack_fw_policy_v2" "accept_test" { + name = "accept_test" + description = "terraform acceptance test" + rules = [ + "${openstack_fw_rule_v2.accept_test_udp_deny.id}", + "${openstack_fw_rule_v2.accept_test_tcp_allow.id}", + "${openstack_fw_rule_v2.accept_test_icmp_allow.id}" + ] +} + +resource "openstack_fw_rule_v2" "accept_test_tcp_allow" { + protocol = "tcp" + action = "allow" +} + +resource "openstack_fw_rule_v2" "accept_test_udp_deny" { + protocol = "udp" + action = "deny" +} + +resource "openstack_fw_rule_v2" "accept_test_icmp_allow" { + protocol = "icmp" + action = "allow" +} +` + +const testFirewallPolicyUpdateDeleteRule = ` +resource "openstack_fw_policy_v2" "accept_test" { + name = "accept_test" + description = "terraform acceptance test" + rules = [ + "${openstack_fw_rule_v2.accept_test_udp_deny.id}" + ] +} + +resource "openstack_fw_rule_v2" "accept_test_udp_deny" { + protocol = "udp" + action = "deny" +} +` From 0ab06af410f9ebd211d1cbcdb082d84f1031b6e4 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Mon, 9 Feb 2015 22:53:20 +0100 Subject: [PATCH 258/295] Add FWaaS firewall acceptance test --- .../resource_openstack_fw_firewall_v2_test.go | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go new file mode 100644 index 000000000..e32ded4c9 --- /dev/null +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go @@ -0,0 +1,151 @@ +package openstack + +import ( + "fmt" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/racker/perigee" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" +) + +func TestAccOpenstackFirewall(t *testing.T) { + + var policyID *string + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckOpenstackFirewallDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testFirewallConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckFirewallExists("openstack_fw_firewall_v2.accept_test", "", "", policyID), + ), + }, + resource.TestStep{ + Config: testFirewallConfigUpdated, + Check: resource.ComposeTestCheckFunc( + testAccCheckFirewallExists("openstack_fw_firewall_v2.accept_test", "accept_test", "terraform acceptance test", policyID), + ), + }, + }, + }) +} + +func testAccCheckOpenstackFirewallDestroy(s *terraform.State) error { + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckOpenstackFirewallDestroy) Error creating OpenStack networking client: %s", err) + } + for _, rs := range s.RootModule().Resources { + if rs.Type != "openstack_firewall" { + continue + } + _, err = firewalls.Get(networkingClient, rs.Primary.ID).Extract() + if err == nil { + return fmt.Errorf("Firewall (%s) still exists.", rs.Primary.ID) + } + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok || httpError.Actual != 404 { + return httpError + } + } + return nil +} + +func testAccCheckFirewallExists(n, expectedName, expectedDescription string, policyID *string) 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") + } + + config := testAccProvider.Meta().(*Config) + networkingClient, err := config.networkingV2Client(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("(testAccCheckFirewallExists) Error creating OpenStack networking client: %s", err) + } + + var found *firewalls.Firewall + for i := 0; i < 5; i++ { + // Firewall creation is asynchronous. Retry some times + // if we get a 404 error. Fail on any other error. + found, err = firewalls.Get(networkingClient, rs.Primary.ID).Extract() + if err != nil { + httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + if !ok || httpError.Actual != 404 { + time.Sleep(time.Second) + continue + } + } + break + } + + if err != nil { + return err + } + + if found.Name != expectedName { + return fmt.Errorf("Expected Name to be <%s> but found <%s>", expectedName, found.Name) + } + if found.Description != expectedDescription { + return fmt.Errorf("Expected Description to be <%s> but found <%s>", expectedDescription, found.Description) + } + if found.PolicyID == "" { + return fmt.Errorf("Policy should not be empty") + } + if policyID != nil && found.PolicyID == *policyID { + return fmt.Errorf("Policy had not been correctly updated. Went from <%s> to <%s>", expectedName, found.Name) + } + + policyID = &found.PolicyID + + return nil + } +} + +const testFirewallConfig = ` +resource "openstack_fw_firewall_v2" "accept_test" { + policy_id = "${openstack_fw_policy_v2.accept_test_policy_1.id}" +} + +resource "openstack_fw_policy_v2" "accept_test_policy_1" { + name = "policy-1" +} +` + +const testFirewallConfigUpdated = ` +resource "openstack_fw_firewall_v2" "accept_test" { + name = "accept_test" + description = "terraform acceptance test" + policy_id = "${openstack_fw_policy_v2.accept_test_policy_1.id}" +} + +resource "openstack_fw_policy_v2" "accept_test_policy_1" { + name = "policy-1" +} +` + +const testFirewallConfigForceNew = ` +resource "openstack_fw_firewall_v2" "accept_test" { + name = "accept_test" + description = "terraform acceptance test" + policy_id = "${openstack_fw_policy_v2.accept_test_policy_2.id}" +} + +resource "openstack_fw_policy_v2" "accept_test_policy_2" { + name = "policy-2" +} +` From cfd3329e00d8c69e3d27909ef81098dd9f3f1327 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Tue, 10 Feb 2015 00:19:01 +0100 Subject: [PATCH 259/295] Add tenant_id attribute on FWaaS resources --- .../resource_openstack_fw_firewall_v2.go | 7 +++++++ .../resource_openstack_fw_policy_v2.go | 19 ++++++++++++++++--- .../resource_openstack_fw_policy_v2_test.go | 3 +++ .../resource_openstack_fw_rule_v2.go | 7 +++++++ .../resource_openstack_fw_rule_v2_test.go | 3 +++ 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go index e2125e7fb..598907ed1 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go @@ -45,6 +45,11 @@ func resourceFWFirewallV2() *schema.Resource { Optional: true, Default: true, }, + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, }, } } @@ -64,6 +69,7 @@ func resourceFirewallCreate(d *schema.ResourceData, meta interface{}) error { Description: d.Get("description").(string), PolicyID: d.Get("policy_id").(string), AdminStateUp: &adminStateUp, + TenantID: d.Get("tenant_id").(string), } log.Printf("[DEBUG] Create firewall: %#v", firewallConfiguration) @@ -90,6 +96,7 @@ func resourceFirewallCreate(d *schema.ResourceData, meta interface{}) error { d.Set("description", firewall.Description) d.Set("policy_id", firewall.PolicyID) d.Set("admin_state_up", firewall.AdminStateUp) + d.Set("tenant_id", firewall.TenantID) _, err = stateConf.WaitForState() diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v2.go b/builtin/providers/openstack/resource_openstack_fw_policy_v2.go index 8f7b593d8..04fa03c07 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v2.go @@ -43,6 +43,11 @@ func resourceFWPolicyV2() *schema.Resource { Optional: true, Default: false, }, + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, "rules": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -76,12 +81,16 @@ func resourceFirewallPolicyCreate(d *schema.ResourceData, meta interface{}) erro rules[i] = v.(string) } + audited := d.Get("audited").(bool) + shared := d.Get("shared").(bool) + opts := policies.CreateOpts{ Name: d.Get("name").(string), Description: d.Get("description").(string), - // Audited: d.Get("audited").(bool), - // Shared: d.Get("shared").(bool), - Rules: rules, + Audited: &audited, + Shared: &shared, + TenantID: d.Get("tenant_id").(string), + Rules: rules, } log.Printf("[DEBUG] Create firewall policy: %#v", opts) @@ -122,6 +131,10 @@ func resourceFirewallPolicyRead(d *schema.ResourceData, meta interface{}) error } d.Set("name", policy.Name) + d.Set("description", policy.Description) + d.Set("shared", policy.Shared) + d.Set("audited", policy.Audited) + d.Set("tenant_id", policy.TenantID) return nil } diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go index 8a3f19ef5..0f0d98e4d 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go @@ -117,6 +117,9 @@ func testAccCheckFirewallPolicyExists(n string, expected *policies.Policy) resou } expected.ID = found.ID + // Erase the tenant id because we don't want to compare + // it as long it is not present in the expected + found.TenantID = "" if !reflect.DeepEqual(expected, found) { return fmt.Errorf("Expected:\n%#v\nFound:\n%#v", expected, found) diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go index 97d8da568..019ee5476 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go @@ -65,6 +65,11 @@ func resourceFWRuleV2() *schema.Resource { Optional: true, Default: true, }, + "tenant_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, }, } } @@ -90,6 +95,7 @@ func resourceFirewallRuleCreate(d *schema.ResourceData, meta interface{}) error SourcePort: d.Get("source_port").(string), DestinationPort: d.Get("destination_port").(string), Enabled: &enabled, + TenantID: d.Get("tenant_id").(string), } log.Printf("[DEBUG] Create firewall rule: %#v", ruleConfiguration) @@ -114,6 +120,7 @@ func resourceFirewallRuleCreate(d *schema.ResourceData, meta interface{}) error d.Set("source_port", rule.SourcePort) d.Set("destination_port", rule.DestinationPort) d.Set("enabled", rule.Enabled) + d.Set("tenant_id", rule.TenantID) return nil } diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go index 86e731b44..5d9d07cb8 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go @@ -135,6 +135,9 @@ func testAccCheckFirewallRuleExists(n string, expected *rules.Rule) resource.Tes } expected.ID = found.ID + // Erase the tenant id because we don't want to compare + // it as long it is not present in the expected + found.TenantID = "" if !reflect.DeepEqual(expected, found) { return fmt.Errorf("Expected:\n%#v\nFound:\n%#v", expected, found) From d6733fb37903d98b73df365997ef9530297662c6 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Tue, 17 Feb 2015 22:07:01 +0100 Subject: [PATCH 260/295] Fix code regarding to the latest gophercloud code --- .../resource_openstack_fw_firewall_v2.go | 11 ++++------ .../resource_openstack_fw_firewall_v2_test.go | 6 ++--- .../resource_openstack_fw_policy_v2.go | 12 +++++----- .../resource_openstack_fw_policy_v2_test.go | 6 ++--- .../resource_openstack_fw_rule_v2.go | 10 ++++----- .../resource_openstack_fw_rule_v2_test.go | 22 +++++++++---------- 6 files changed, 30 insertions(+), 37 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go index 598907ed1..cab83ce63 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/racker/perigee" "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" ) @@ -114,7 +113,7 @@ func resourceFirewallRead(d *schema.ResourceData, meta interface{}) error { firewall, err := firewalls.Get(networkingClient, d.Id()).Extract() if err != nil { - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok { return err } @@ -145,13 +144,11 @@ func resourceFirewallUpdate(d *schema.ResourceData, meta interface{}) error { opts := firewalls.UpdateOpts{} if d.HasChange("name") { - name := d.Get("name").(string) - opts.Name = &name + opts.Name = d.Get("name").(string) } if d.HasChange("description") { - description := d.Get("description").(string) - opts.Description = &description + opts.Description = d.Get("description").(string) } if d.HasChange("policy_id") { @@ -227,7 +224,7 @@ func WaitForFirewallDeletion(networkingClient *gophercloud.ServiceClient, id str log.Printf("[DEBUG] Get firewall %s => %#v", id, fw) if err != nil { - httpStatus := err.(*perigee.UnexpectedResponseCodeError) + httpStatus := err.(*gophercloud.UnexpectedResponseCodeError) log.Printf("[DEBUG] Get firewall %s status is %d", id, httpStatus.Actual) if httpStatus.Actual == 404 { diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go index e32ded4c9..a0adecdc4 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/racker/perigee" + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" ) @@ -51,7 +51,7 @@ func testAccCheckOpenstackFirewallDestroy(s *terraform.State) error { if err == nil { return fmt.Errorf("Firewall (%s) still exists.", rs.Primary.ID) } - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok || httpError.Actual != 404 { return httpError } @@ -84,7 +84,7 @@ func testAccCheckFirewallExists(n, expectedName, expectedDescription string, pol // if we get a 404 error. Fail on any other error. found, err = firewalls.Get(networkingClient, rs.Primary.ID).Extract() if err != nil { - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok || httpError.Actual != 404 { time.Sleep(time.Second) continue diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v2.go b/builtin/providers/openstack/resource_openstack_fw_policy_v2.go index 04fa03c07..c4d05bb8e 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v2.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" - "github.com/racker/perigee" + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies" ) @@ -119,7 +119,7 @@ func resourceFirewallPolicyRead(d *schema.ResourceData, meta interface{}) error policy, err := policies.Get(networkingClient, d.Id()).Extract() if err != nil { - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok { return err } @@ -150,13 +150,11 @@ func resourceFirewallPolicyUpdate(d *schema.ResourceData, meta interface{}) erro opts := policies.UpdateOpts{} if d.HasChange("name") { - name := d.Get("name").(string) - opts.Name = &name + opts.Name = d.Get("name").(string) } if d.HasChange("description") { - description := d.Get("description").(string) - opts.Description = &description + opts.Description = d.Get("description").(string) } if d.HasChange("rules") { @@ -193,7 +191,7 @@ func resourceFirewallPolicyDelete(d *schema.ResourceData, meta interface{}) erro break } - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok || httpError.Actual != 409 { return err } diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go index 0f0d98e4d..58c1c1ac1 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/racker/perigee" + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies" ) @@ -70,7 +70,7 @@ func testAccCheckOpenstackFirewallPolicyDestroy(s *terraform.State) error { if err == nil { return fmt.Errorf("Firewall policy (%s) still exists.", rs.Primary.ID) } - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok || httpError.Actual != 404 { return httpError } @@ -103,7 +103,7 @@ func testAccCheckFirewallPolicyExists(n string, expected *policies.Policy) resou // if we get a 404 error. Fail on any other error. found, err = policies.Get(networkingClient, rs.Primary.ID).Extract() if err != nil { - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok || httpError.Actual != 404 { time.Sleep(time.Second) continue diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go index 019ee5476..3d6b4a51b 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go @@ -5,7 +5,7 @@ import ( "log" "github.com/hashicorp/terraform/helper/schema" - "github.com/racker/perigee" + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules" ) @@ -137,7 +137,7 @@ func resourceFirewallRuleRead(d *schema.ResourceData, meta interface{}) error { rule, err := rules.Get(networkingClient, d.Id()).Extract() if err != nil { - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok { return err } @@ -172,12 +172,10 @@ func resourceFirewallRuleUpdate(d *schema.ResourceData, meta interface{}) error opts := rules.UpdateOpts{} if d.HasChange("name") { - name := d.Get("name").(string) - opts.Name = &name + opts.Name = d.Get("name").(string) } if d.HasChange("description") { - description := d.Get("description").(string) - opts.Description = &description + opts.Description = d.Get("description").(string) } if d.HasChange("protocol") { opts.Protocol = d.Get("protocol").(string) diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go index 5d9d07cb8..748177375 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/racker/perigee" + "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules" ) @@ -88,7 +88,7 @@ func testAccCheckOpenstackFirewallRuleDestroy(s *terraform.State) error { if err == nil { return fmt.Errorf("Firewall rule (%s) still exists.", rs.Primary.ID) } - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok || httpError.Actual != 404 { return httpError } @@ -121,7 +121,7 @@ func testAccCheckFirewallRuleExists(n string, expected *rules.Rule) resource.Tes // if we get a 404 error. Fail on any other error. found, err = rules.Get(networkingClient, rs.Primary.ID).Extract() if err != nil { - httpError, ok := err.(*perigee.UnexpectedResponseCodeError) + httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) if !ok || httpError.Actual != 404 { time.Sleep(time.Second) continue @@ -149,17 +149,17 @@ func testAccCheckFirewallRuleExists(n string, expected *rules.Rule) resource.Tes const testFirewallRuleMinimalConfig = ` resource "openstack_fw_rule_v2" "accept_test_minimal" { - protocol = "udp" - action = "deny" + protocol = "udp" + action = "deny" } ` const testFirewallRuleConfig = ` resource "openstack_fw_rule_v2" "accept_test" { - name = "accept_test" + name = "accept_test" description = "Terraform accept test" - protocol = "udp" - action = "deny" + protocol = "udp" + action = "deny" ip_version = 4 source_ip_address = "1.2.3.4" destination_ip_address = "4.3.2.0/24" @@ -171,10 +171,10 @@ resource "openstack_fw_rule_v2" "accept_test" { const testFirewallRuleUpdateAllFieldsConfig = ` resource "openstack_fw_rule_v2" "accept_test" { - name = "accept_test_updated_2" + name = "accept_test_updated_2" description = "Terraform accept test updated" - protocol = "tcp" - action = "allow" + protocol = "tcp" + action = "allow" ip_version = 4 source_ip_address = "1.2.3.0/24" destination_ip_address = "4.3.2.8" From 1c981d6f30fe1fb742cecbcb7fb6b58e93bb1f8e Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Wed, 18 Feb 2015 00:12:04 +0100 Subject: [PATCH 261/295] Fix race conditions on firewall state transition --- .../resource_openstack_fw_firewall_v2.go | 38 +++++++++++-------- .../resource_openstack_fw_firewall_v2_test.go | 12 ------ 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go index cab83ce63..a216b506b 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go @@ -37,7 +37,6 @@ func resourceFWFirewallV2() *schema.Resource { "policy_id": &schema.Schema{ Type: schema.TypeString, Required: true, - ForceNew: true, }, "admin_state_up": &schema.Schema{ Type: schema.TypeBool, @@ -89,16 +88,10 @@ func resourceFirewallCreate(d *schema.ResourceData, meta interface{}) error { MinTimeout: 2 * time.Second, } - d.SetId(firewall.ID) - - d.Set("name", firewall.Name) - d.Set("description", firewall.Description) - d.Set("policy_id", firewall.PolicyID) - d.Set("admin_state_up", firewall.AdminStateUp) - d.Set("tenant_id", firewall.TenantID) - _, err = stateConf.WaitForState() + d.SetId(firewall.ID) + return nil } @@ -129,6 +122,7 @@ func resourceFirewallRead(d *schema.ResourceData, meta interface{}) error { d.Set("description", firewall.Description) d.Set("policy_id", firewall.PolicyID) d.Set("admin_state_up", firewall.AdminStateUp) + d.Set("tenant_id", firewall.TenantID) return nil } @@ -155,14 +149,15 @@ func resourceFirewallUpdate(d *schema.ResourceData, meta interface{}) error { opts.PolicyID = d.Get("policy_id").(string) } - log.Printf("[DEBUG] Updating firewall with id %s: %#v", d.Id(), opts) - - if err := firewalls.Update(networkingClient, d.Id(), opts).Err; err != nil { - return err + if d.HasChange("admin_state_up") { + adminStateUp := d.Get("admin_state_up").(bool) + opts.AdminStateUp = &adminStateUp } + log.Printf("[DEBUG] Updating firewall with id %s: %#v", d.Id(), opts) + stateConf := &resource.StateChangeConf{ - Pending: []string{"PENDING_CREATE"}, + Pending: []string{"PENDING_CREATE", "PENDING_UPDATE"}, Target: "ACTIVE", Refresh: WaitForFirewallActive(networkingClient, d.Id()), Timeout: 30 * time.Second, @@ -172,7 +167,7 @@ func resourceFirewallUpdate(d *schema.ResourceData, meta interface{}) error { _, err = stateConf.WaitForState() - return err + return firewalls.Update(networkingClient, d.Id(), opts).Err } func resourceFirewallDelete(d *schema.ResourceData, meta interface{}) error { @@ -184,13 +179,24 @@ func resourceFirewallDelete(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } + stateConf := &resource.StateChangeConf{ + Pending: []string{"PENDING_CREATE", "PENDING_UPDATE"}, + Target: "ACTIVE", + Refresh: WaitForFirewallActive(networkingClient, d.Id()), + Timeout: 30 * time.Second, + Delay: 0, + MinTimeout: 2 * time.Second, + } + + _, err = stateConf.WaitForState() + err = firewalls.Delete(networkingClient, d.Id()).Err if err != nil { return err } - stateConf := &resource.StateChangeConf{ + stateConf = &resource.StateChangeConf{ Pending: []string{"DELETING"}, Target: "DELETED", Refresh: WaitForFirewallDeletion(networkingClient, d.Id()), diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go index a0adecdc4..cc5343660 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go @@ -127,18 +127,6 @@ resource "openstack_fw_policy_v2" "accept_test_policy_1" { ` const testFirewallConfigUpdated = ` -resource "openstack_fw_firewall_v2" "accept_test" { - name = "accept_test" - description = "terraform acceptance test" - policy_id = "${openstack_fw_policy_v2.accept_test_policy_1.id}" -} - -resource "openstack_fw_policy_v2" "accept_test_policy_1" { - name = "policy-1" -} -` - -const testFirewallConfigForceNew = ` resource "openstack_fw_firewall_v2" "accept_test" { name = "accept_test" description = "terraform acceptance test" From c5e861c0491dd03b3e701c0daf11164561a537a4 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Wed, 18 Feb 2015 00:52:54 +0100 Subject: [PATCH 262/295] Remove useless code --- .../openstack/resource_openstack_fw_rule_v2.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go index 3d6b4a51b..f3aacf510 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go @@ -110,18 +110,6 @@ func resourceFirewallRuleCreate(d *schema.ResourceData, meta interface{}) error d.SetId(rule.ID) - d.Set("name", rule.Name) - d.Set("description", rule.Description) - d.Set("protocol", rule.Protocol) - d.Set("action", rule.Action) - d.Set("ip_version", rule.IPVersion) - d.Set("source_ip_address", rule.SourceIPAddress) - d.Set("destination_ip_address", rule.DestinationIPAddress) - d.Set("source_port", rule.SourcePort) - d.Set("destination_port", rule.DestinationPort) - d.Set("enabled", rule.Enabled) - d.Set("tenant_id", rule.TenantID) - return nil } From 54174dcc05efe3b560e105053eb3b90d0405af5c Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Wed, 18 Feb 2015 01:00:48 +0100 Subject: [PATCH 263/295] Fix firewall policies tests --- .../resource_openstack_fw_policy_v2_test.go | 41 +++++++------------ 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go index 58c1c1ac1..ed268b7c3 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go @@ -2,7 +2,6 @@ package openstack import ( "fmt" - "reflect" "testing" "time" @@ -23,9 +22,7 @@ func TestAccOpenstackFirewallPolicy(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckFirewallPolicyExists( "openstack_fw_policy_v2.accept_test", - &policies.Policy{ - Rules: []string{}, - }), + "", "", 0), ), }, resource.TestStep{ @@ -33,14 +30,7 @@ func TestAccOpenstackFirewallPolicy(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckFirewallPolicyExists( "openstack_fw_policy_v2.accept_test", - &policies.Policy{ - Name: "accept_test", - Description: "terraform acceptance test", - Rules: []string{ - "", - "", - }, - }), + "accept_test", "terraform acceptance test", 2), ), }, resource.TestStep{ @@ -48,7 +38,7 @@ func TestAccOpenstackFirewallPolicy(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckFirewallPolicyExists( "openstack_fw_policy_v2.accept_test", - &policies.Policy{}), + "accept_test", "terraform acceptance test", 1), ), }, }, @@ -78,7 +68,7 @@ func testAccCheckOpenstackFirewallPolicyDestroy(s *terraform.State) error { return nil } -func testAccCheckFirewallPolicyExists(n string, expected *policies.Policy) resource.TestCheckFunc { +func testAccCheckFirewallPolicyExists(n, name, description string, ruleCount int) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -116,13 +106,16 @@ func testAccCheckFirewallPolicyExists(n string, expected *policies.Policy) resou return err } - expected.ID = found.ID - // Erase the tenant id because we don't want to compare - // it as long it is not present in the expected - found.TenantID = "" + if name != found.Name { + return fmt.Errorf("Expected name <%s>, but found <%s>", name, found.Name) + } - if !reflect.DeepEqual(expected, found) { - return fmt.Errorf("Expected:\n%#v\nFound:\n%#v", expected, found) + if description != found.Description { + return fmt.Errorf("Expected description <%s>, but found <%s>", description, found.Description) + } + + if ruleCount != len(found.Rules) { + return fmt.Errorf("Expected rule count <%d>, but found <%d>", ruleCount, len(found.Rules)) } return nil @@ -141,8 +134,7 @@ resource "openstack_fw_policy_v2" "accept_test" { description = "terraform acceptance test" rules = [ "${openstack_fw_rule_v2.accept_test_udp_deny.id}", - "${openstack_fw_rule_v2.accept_test_tcp_allow.id}", - "${openstack_fw_rule_v2.accept_test_icmp_allow.id}" + "${openstack_fw_rule_v2.accept_test_tcp_allow.id}" ] } @@ -155,11 +147,6 @@ resource "openstack_fw_rule_v2" "accept_test_udp_deny" { protocol = "udp" action = "deny" } - -resource "openstack_fw_rule_v2" "accept_test_icmp_allow" { - protocol = "icmp" - action = "allow" -} ` const testFirewallPolicyUpdateDeleteRule = ` From ed31588b8428f83f352a88f5d2e0f68d4a7ad20f Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Wed, 18 Feb 2015 01:01:46 +0100 Subject: [PATCH 264/295] Unassociate firewall rule from policy before delete --- .../openstack/resource_openstack_fw_rule_v2.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go index f3aacf510..4a50303e1 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud" + "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules" ) @@ -208,5 +209,18 @@ func resourceFirewallRuleDelete(d *schema.ResourceData, meta interface{}) error if err != nil { return fmt.Errorf("Error creating OpenStack networking client: %s", err) } + + rule, err := rules.Get(networkingClient, d.Id()).Extract() + if err != nil { + return err + } + + if rule.PolicyID != "" { + err := policies.RemoveRule(networkingClient, rule.PolicyID, rule.ID) + if err != nil { + return err + } + } + return rules.Delete(networkingClient, d.Id()).Err } From 1efaaeeca62738f3c35dd556a08c2db430353500 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Wed, 18 Feb 2015 01:27:45 +0100 Subject: [PATCH 265/295] Use d.GetOk to populate data in read operations --- .../resource_openstack_fw_firewall_v2.go | 34 +++++++++-- .../resource_openstack_fw_policy_v2.go | 34 +++++++++-- .../resource_openstack_fw_rule_v2.go | 56 ++++++++++++++++--- 3 files changed, 106 insertions(+), 18 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go index a216b506b..2d10fdc3c 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go @@ -118,11 +118,35 @@ func resourceFirewallRead(d *schema.ResourceData, meta interface{}) error { return err } - d.Set("name", firewall.Name) - d.Set("description", firewall.Description) - d.Set("policy_id", firewall.PolicyID) - d.Set("admin_state_up", firewall.AdminStateUp) - d.Set("tenant_id", firewall.TenantID) + if t, exists := d.GetOk("name"); exists && t != "" { + d.Set("name", firewall.Name) + } else { + d.Set("name", "") + } + + if t, exists := d.GetOk("description"); exists && t != "" { + d.Set("description", firewall.Description) + } else { + d.Set("description", "") + } + + if t, exists := d.GetOk("policy_id"); exists && t != "" { + d.Set("policy_id", firewall.PolicyID) + } else { + d.Set("policy_id", "") + } + + if t, exists := d.GetOk("admin_state_up"); exists && t != "" { + d.Set("admin_state_up", firewall.AdminStateUp) + } else { + d.Set("admin_state_up", "") + } + + if t, exists := d.GetOk("tenant_id"); exists && t != "" { + d.Set("tenant_id", firewall.TenantID) + } else { + d.Set("tenant_id", "") + } return nil } diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v2.go b/builtin/providers/openstack/resource_openstack_fw_policy_v2.go index c4d05bb8e..ad096397b 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v2.go @@ -130,11 +130,35 @@ func resourceFirewallPolicyRead(d *schema.ResourceData, meta interface{}) error return err } - d.Set("name", policy.Name) - d.Set("description", policy.Description) - d.Set("shared", policy.Shared) - d.Set("audited", policy.Audited) - d.Set("tenant_id", policy.TenantID) + if t, exists := d.GetOk("name"); exists && t != "" { + d.Set("name", policy.Name) + } else { + d.Set("name", "") + } + + if t, exists := d.GetOk("description"); exists && t != "" { + d.Set("description", policy.Description) + } else { + d.Set("description", "") + } + + if t, exists := d.GetOk("shared"); exists && t != "" { + d.Set("shared", policy.Shared) + } else { + d.Set("shared", "") + } + + if t, exists := d.GetOk("audited"); exists && t != "" { + d.Set("audited", policy.Audited) + } else { + d.Set("audited", "") + } + + if t, exists := d.GetOk("tenant_id"); exists && t != "" { + d.Set("tenant_id", policy.TenantID) + } else { + d.Set("tenant_id", "") + } return nil } diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go index 4a50303e1..9aa110757 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v2.go @@ -137,16 +137,56 @@ func resourceFirewallRuleRead(d *schema.ResourceData, meta interface{}) error { return err } - d.Set("name", rule.Name) - d.Set("description", rule.Description) d.Set("protocol", rule.Protocol) d.Set("action", rule.Action) - d.Set("ip_version", rule.IPVersion) - d.Set("source_ip_address", rule.SourceIPAddress) - d.Set("destination_ip_address", rule.DestinationIPAddress) - d.Set("source_port", rule.SourcePort) - d.Set("destination_port", rule.DestinationPort) - d.Set("enabled", rule.Enabled) + + if t, exists := d.GetOk("name"); exists && t != "" { + d.Set("name", rule.Name) + } else { + d.Set("name", "") + } + + if t, exists := d.GetOk("description"); exists && t != "" { + d.Set("description", rule.Description) + } else { + d.Set("description", "") + } + + if t, exists := d.GetOk("ip_version"); exists && t != "" { + d.Set("ip_version", rule.IPVersion) + } else { + d.Set("ip_version", "") + } + + if t, exists := d.GetOk("source_ip_address"); exists && t != "" { + d.Set("source_ip_address", rule.SourceIPAddress) + } else { + d.Set("source_ip_address", "") + } + + if t, exists := d.GetOk("destination_ip_address"); exists && t != "" { + d.Set("destination_ip_address", rule.DestinationIPAddress) + } else { + d.Set("destination_ip_address", "") + } + + if t, exists := d.GetOk("source_port"); exists && t != "" { + d.Set("source_port", rule.SourcePort) + } else { + d.Set("source_port", "") + } + + if t, exists := d.GetOk("destination_port"); exists && t != "" { + d.Set("destination_port", rule.DestinationPort) + } else { + d.Set("destination_port", "") + } + + if t, exists := d.GetOk("enabled"); exists && t != "" { + d.Set("enabled", rule.Enabled) + } else { + d.Set("enabled", "") + } return nil } From 16a963313fe21196660ab83f2f061799668fe66a Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Thu, 19 Feb 2015 22:55:54 +0100 Subject: [PATCH 266/295] FWaaS version is actually v1 not v2 Rename files and methods. Confusion have been made between neutron version and FWaaS extension version. --- builtin/providers/openstack/provider.go | 6 +-- ...o => resource_openstack_fw_firewall_v1.go} | 30 +++++++------- ...resource_openstack_fw_firewall_v1_test.go} | 24 +++++------ ....go => resource_openstack_fw_policy_v1.go} | 18 ++++----- ...> resource_openstack_fw_policy_v1_test.go} | 40 +++++++++---------- ...v2.go => resource_openstack_fw_rule_v1.go} | 18 ++++----- ... => resource_openstack_fw_rule_v1_test.go} | 26 ++++++------ 7 files changed, 81 insertions(+), 81 deletions(-) rename builtin/providers/openstack/{resource_openstack_fw_firewall_v2.go => resource_openstack_fw_firewall_v1.go} (87%) rename builtin/providers/openstack/{resource_openstack_fw_firewall_v2_test.go => resource_openstack_fw_firewall_v1_test.go} (78%) rename builtin/providers/openstack/{resource_openstack_fw_policy_v2.go => resource_openstack_fw_policy_v1.go} (90%) rename builtin/providers/openstack/{resource_openstack_fw_policy_v2_test.go => resource_openstack_fw_policy_v1_test.go} (75%) rename builtin/providers/openstack/{resource_openstack_fw_rule_v2.go => resource_openstack_fw_rule_v1.go} (92%) rename builtin/providers/openstack/{resource_openstack_fw_rule_v2_test.go => resource_openstack_fw_rule_v1_test.go} (86%) diff --git a/builtin/providers/openstack/provider.go b/builtin/providers/openstack/provider.go index 7e0880007..a43242333 100644 --- a/builtin/providers/openstack/provider.go +++ b/builtin/providers/openstack/provider.go @@ -64,9 +64,9 @@ func Provider() terraform.ResourceProvider { "openstack_compute_keypair_v2": resourceComputeKeypairV2(), "openstack_compute_secgroup_v2": resourceComputeSecGroupV2(), "openstack_compute_floatingip_v2": resourceComputeFloatingIPV2(), - "openstack_fw_firewall_v2": resourceFWFirewallV2(), - "openstack_fw_policy_v2": resourceFWPolicyV2(), - "openstack_fw_rule_v2": resourceFWRuleV2(), + "openstack_fw_firewall_v1": resourceFWFirewallV1(), + "openstack_fw_policy_v1": resourceFWPolicyV1(), + "openstack_fw_rule_v1": resourceFWRuleV1(), "openstack_lb_monitor_v1": resourceLBMonitorV1(), "openstack_lb_pool_v1": resourceLBPoolV1(), "openstack_lb_vip_v1": resourceLBVipV1(), diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go similarity index 87% rename from builtin/providers/openstack/resource_openstack_fw_firewall_v2.go rename to builtin/providers/openstack/resource_openstack_fw_firewall_v1.go index 2d10fdc3c..21c93d33d 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go @@ -12,12 +12,12 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" ) -func resourceFWFirewallV2() *schema.Resource { +func resourceFWFirewallV1() *schema.Resource { return &schema.Resource{ - Create: resourceFirewallCreate, - Read: resourceFirewallRead, - Update: resourceFirewallUpdate, - Delete: resourceFirewallDelete, + Create: resourceFWFirewallV1Create, + Read: resourceFWFirewallV1Read, + Update: resourceFWFirewallV1Update, + Delete: resourceFWFirewallV1Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -52,7 +52,7 @@ func resourceFWFirewallV2() *schema.Resource { } } -func resourceFirewallCreate(d *schema.ResourceData, meta interface{}) error { +func resourceFWFirewallV1Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := config.networkingV2Client(d.Get("region").(string)) @@ -82,7 +82,7 @@ func resourceFirewallCreate(d *schema.ResourceData, meta interface{}) error { stateConf := &resource.StateChangeConf{ Pending: []string{"PENDING_CREATE"}, Target: "ACTIVE", - Refresh: WaitForFirewallActive(networkingClient, firewall.ID), + Refresh: waitForFirewallActive(networkingClient, firewall.ID), Timeout: 30 * time.Second, Delay: 0, MinTimeout: 2 * time.Second, @@ -95,7 +95,7 @@ func resourceFirewallCreate(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceFirewallRead(d *schema.ResourceData, meta interface{}) error { +func resourceFWFirewallV1Read(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Retrieve information about firewall: %s", d.Id()) config := meta.(*Config) @@ -151,7 +151,7 @@ func resourceFirewallRead(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceFirewallUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceFWFirewallV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := config.networkingV2Client(d.Get("region").(string)) @@ -183,7 +183,7 @@ func resourceFirewallUpdate(d *schema.ResourceData, meta interface{}) error { stateConf := &resource.StateChangeConf{ Pending: []string{"PENDING_CREATE", "PENDING_UPDATE"}, Target: "ACTIVE", - Refresh: WaitForFirewallActive(networkingClient, d.Id()), + Refresh: waitForFirewallActive(networkingClient, d.Id()), Timeout: 30 * time.Second, Delay: 0, MinTimeout: 2 * time.Second, @@ -194,7 +194,7 @@ func resourceFirewallUpdate(d *schema.ResourceData, meta interface{}) error { return firewalls.Update(networkingClient, d.Id(), opts).Err } -func resourceFirewallDelete(d *schema.ResourceData, meta interface{}) error { +func resourceFWFirewallV1Delete(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Destroy firewall: %s", d.Id()) config := meta.(*Config) @@ -206,7 +206,7 @@ func resourceFirewallDelete(d *schema.ResourceData, meta interface{}) error { stateConf := &resource.StateChangeConf{ Pending: []string{"PENDING_CREATE", "PENDING_UPDATE"}, Target: "ACTIVE", - Refresh: WaitForFirewallActive(networkingClient, d.Id()), + Refresh: waitForFirewallActive(networkingClient, d.Id()), Timeout: 30 * time.Second, Delay: 0, MinTimeout: 2 * time.Second, @@ -223,7 +223,7 @@ func resourceFirewallDelete(d *schema.ResourceData, meta interface{}) error { stateConf = &resource.StateChangeConf{ Pending: []string{"DELETING"}, Target: "DELETED", - Refresh: WaitForFirewallDeletion(networkingClient, d.Id()), + Refresh: waitForFirewallDeletion(networkingClient, d.Id()), Timeout: 2 * time.Minute, Delay: 0, MinTimeout: 2 * time.Second, @@ -234,7 +234,7 @@ func resourceFirewallDelete(d *schema.ResourceData, meta interface{}) error { return err } -func WaitForFirewallActive(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { +func waitForFirewallActive(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { fw, err := firewalls.Get(networkingClient, id).Extract() @@ -247,7 +247,7 @@ func WaitForFirewallActive(networkingClient *gophercloud.ServiceClient, id strin } } -func WaitForFirewallDeletion(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { +func waitForFirewallDeletion(networkingClient *gophercloud.ServiceClient, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { fw, err := firewalls.Get(networkingClient, id).Extract() diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v1_test.go similarity index 78% rename from builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go rename to builtin/providers/openstack/resource_openstack_fw_firewall_v1_test.go index cc5343660..34112f778 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v1_test.go @@ -11,32 +11,32 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/firewalls" ) -func TestAccOpenstackFirewall(t *testing.T) { +func TestAccFWFirewallV1(t *testing.T) { var policyID *string resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: testAccCheckOpenstackFirewallDestroy, + CheckDestroy: testAccCheckFWFirewallV1Destroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testFirewallConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckFirewallExists("openstack_fw_firewall_v2.accept_test", "", "", policyID), + testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.accept_test", "", "", policyID), ), }, resource.TestStep{ Config: testFirewallConfigUpdated, Check: resource.ComposeTestCheckFunc( - testAccCheckFirewallExists("openstack_fw_firewall_v2.accept_test", "accept_test", "terraform acceptance test", policyID), + testAccCheckFWFirewallV1Exists("openstack_fw_firewall_v1.accept_test", "accept_test", "terraform acceptance test", policyID), ), }, }, }) } -func testAccCheckOpenstackFirewallDestroy(s *terraform.State) error { +func testAccCheckFWFirewallV1Destroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) networkingClient, err := config.networkingV2Client(OS_REGION_NAME) @@ -59,7 +59,7 @@ func testAccCheckOpenstackFirewallDestroy(s *terraform.State) error { return nil } -func testAccCheckFirewallExists(n, expectedName, expectedDescription string, policyID *string) resource.TestCheckFunc { +func testAccCheckFWFirewallV1Exists(n, expectedName, expectedDescription string, policyID *string) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -117,23 +117,23 @@ func testAccCheckFirewallExists(n, expectedName, expectedDescription string, pol } const testFirewallConfig = ` -resource "openstack_fw_firewall_v2" "accept_test" { - policy_id = "${openstack_fw_policy_v2.accept_test_policy_1.id}" +resource "openstack_fw_firewall_v1" "accept_test" { + policy_id = "${openstack_fw_policy_v1.accept_test_policy_1.id}" } -resource "openstack_fw_policy_v2" "accept_test_policy_1" { +resource "openstack_fw_policy_v1" "accept_test_policy_1" { name = "policy-1" } ` const testFirewallConfigUpdated = ` -resource "openstack_fw_firewall_v2" "accept_test" { +resource "openstack_fw_firewall_v1" "accept_test" { name = "accept_test" description = "terraform acceptance test" - policy_id = "${openstack_fw_policy_v2.accept_test_policy_2.id}" + policy_id = "${openstack_fw_policy_v1.accept_test_policy_2.id}" } -resource "openstack_fw_policy_v2" "accept_test_policy_2" { +resource "openstack_fw_policy_v1" "accept_test_policy_2" { name = "policy-2" } ` diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v2.go b/builtin/providers/openstack/resource_openstack_fw_policy_v1.go similarity index 90% rename from builtin/providers/openstack/resource_openstack_fw_policy_v2.go rename to builtin/providers/openstack/resource_openstack_fw_policy_v1.go index ad096397b..e9fc1645b 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v1.go @@ -11,12 +11,12 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies" ) -func resourceFWPolicyV2() *schema.Resource { +func resourceFWPolicyV1() *schema.Resource { return &schema.Resource{ - Create: resourceFirewallPolicyCreate, - Read: resourceFirewallPolicyRead, - Update: resourceFirewallPolicyUpdate, - Delete: resourceFirewallPolicyDelete, + Create: resourceFWPolicyV1Create, + Read: resourceFWPolicyV1Read, + Update: resourceFWPolicyV1Update, + Delete: resourceFWPolicyV1Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -60,7 +60,7 @@ func resourceFWPolicyV2() *schema.Resource { } } -func resourceFirewallPolicyCreate(d *schema.ResourceData, meta interface{}) error { +func resourceFWPolicyV1Create(d *schema.ResourceData, meta interface{}) error { // TODO To remove time.Sleep(time.Second * 5) @@ -107,7 +107,7 @@ func resourceFirewallPolicyCreate(d *schema.ResourceData, meta interface{}) erro return nil } -func resourceFirewallPolicyRead(d *schema.ResourceData, meta interface{}) error { +func resourceFWPolicyV1Read(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Retrieve information about firewall policy: %s", d.Id()) config := meta.(*Config) @@ -163,7 +163,7 @@ func resourceFirewallPolicyRead(d *schema.ResourceData, meta interface{}) error return nil } -func resourceFirewallPolicyUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceFWPolicyV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := config.networkingV2Client(d.Get("region").(string)) @@ -199,7 +199,7 @@ func resourceFirewallPolicyUpdate(d *schema.ResourceData, meta interface{}) erro return policies.Update(networkingClient, d.Id(), opts).Err } -func resourceFirewallPolicyDelete(d *schema.ResourceData, meta interface{}) error { +func resourceFWPolicyV1Delete(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Destroy firewall policy: %s", d.Id()) config := meta.(*Config) diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_policy_v1_test.go similarity index 75% rename from builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go rename to builtin/providers/openstack/resource_openstack_fw_policy_v1_test.go index ed268b7c3..1a37a383f 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v1_test.go @@ -11,33 +11,33 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies" ) -func TestAccOpenstackFirewallPolicy(t *testing.T) { +func TestAccFWPolicyV1(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: testAccCheckOpenstackFirewallPolicyDestroy, + CheckDestroy: testAccCheckFWPolicyV1Destroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testFirewallPolicyConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckFirewallPolicyExists( - "openstack_fw_policy_v2.accept_test", + testAccCheckFWPolicyV1Exists( + "openstack_fw_policy_v1.accept_test", "", "", 0), ), }, resource.TestStep{ Config: testFirewallPolicyConfigAddRules, Check: resource.ComposeTestCheckFunc( - testAccCheckFirewallPolicyExists( - "openstack_fw_policy_v2.accept_test", + testAccCheckFWPolicyV1Exists( + "openstack_fw_policy_v1.accept_test", "accept_test", "terraform acceptance test", 2), ), }, resource.TestStep{ Config: testFirewallPolicyUpdateDeleteRule, Check: resource.ComposeTestCheckFunc( - testAccCheckFirewallPolicyExists( - "openstack_fw_policy_v2.accept_test", + testAccCheckFWPolicyV1Exists( + "openstack_fw_policy_v1.accept_test", "accept_test", "terraform acceptance test", 1), ), }, @@ -45,7 +45,7 @@ func TestAccOpenstackFirewallPolicy(t *testing.T) { }) } -func testAccCheckOpenstackFirewallPolicyDestroy(s *terraform.State) error { +func testAccCheckFWPolicyV1Destroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) networkingClient, err := config.networkingV2Client(OS_REGION_NAME) @@ -53,7 +53,7 @@ func testAccCheckOpenstackFirewallPolicyDestroy(s *terraform.State) error { return fmt.Errorf("(testAccCheckOpenstackFirewallPolicyDestroy) Error creating OpenStack networking client: %s", err) } for _, rs := range s.RootModule().Resources { - if rs.Type != "openstack_fw_policy_v2" { + if rs.Type != "openstack_fw_policy_v1" { continue } _, err = policies.Get(networkingClient, rs.Primary.ID).Extract() @@ -68,7 +68,7 @@ func testAccCheckOpenstackFirewallPolicyDestroy(s *terraform.State) error { return nil } -func testAccCheckFirewallPolicyExists(n, name, description string, ruleCount int) resource.TestCheckFunc { +func testAccCheckFWPolicyV1Exists(n, name, description string, ruleCount int) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -123,42 +123,42 @@ func testAccCheckFirewallPolicyExists(n, name, description string, ruleCount int } const testFirewallPolicyConfig = ` -resource "openstack_fw_policy_v2" "accept_test" { +resource "openstack_fw_policy_v1" "accept_test" { } ` const testFirewallPolicyConfigAddRules = ` -resource "openstack_fw_policy_v2" "accept_test" { +resource "openstack_fw_policy_v1" "accept_test" { name = "accept_test" description = "terraform acceptance test" rules = [ - "${openstack_fw_rule_v2.accept_test_udp_deny.id}", - "${openstack_fw_rule_v2.accept_test_tcp_allow.id}" + "${openstack_fw_rule_v1.accept_test_udp_deny.id}", + "${openstack_fw_rule_v1.accept_test_tcp_allow.id}" ] } -resource "openstack_fw_rule_v2" "accept_test_tcp_allow" { +resource "openstack_fw_rule_v1" "accept_test_tcp_allow" { protocol = "tcp" action = "allow" } -resource "openstack_fw_rule_v2" "accept_test_udp_deny" { +resource "openstack_fw_rule_v1" "accept_test_udp_deny" { protocol = "udp" action = "deny" } ` const testFirewallPolicyUpdateDeleteRule = ` -resource "openstack_fw_policy_v2" "accept_test" { +resource "openstack_fw_policy_v1" "accept_test" { name = "accept_test" description = "terraform acceptance test" rules = [ - "${openstack_fw_rule_v2.accept_test_udp_deny.id}" + "${openstack_fw_rule_v1.accept_test_udp_deny.id}" ] } -resource "openstack_fw_rule_v2" "accept_test_udp_deny" { +resource "openstack_fw_rule_v1" "accept_test_udp_deny" { protocol = "udp" action = "deny" } diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go b/builtin/providers/openstack/resource_openstack_fw_rule_v1.go similarity index 92% rename from builtin/providers/openstack/resource_openstack_fw_rule_v2.go rename to builtin/providers/openstack/resource_openstack_fw_rule_v1.go index 9aa110757..bd418e9b8 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v2.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v1.go @@ -10,12 +10,12 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules" ) -func resourceFWRuleV2() *schema.Resource { +func resourceFWRuleV1() *schema.Resource { return &schema.Resource{ - Create: resourceFirewallRuleCreate, - Read: resourceFirewallRuleRead, - Update: resourceFirewallRuleUpdate, - Delete: resourceFirewallRuleDelete, + Create: resourceFWRuleV1Create, + Read: resourceFWRuleV1Read, + Update: resourceFWRuleV1Update, + Delete: resourceFWRuleV1Delete, Schema: map[string]*schema.Schema{ "region": &schema.Schema{ @@ -75,7 +75,7 @@ func resourceFWRuleV2() *schema.Resource { } } -func resourceFirewallRuleCreate(d *schema.ResourceData, meta interface{}) error { +func resourceFWRuleV1Create(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := config.networkingV2Client(d.Get("region").(string)) @@ -114,7 +114,7 @@ func resourceFirewallRuleCreate(d *schema.ResourceData, meta interface{}) error return nil } -func resourceFirewallRuleRead(d *schema.ResourceData, meta interface{}) error { +func resourceFWRuleV1Read(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Retrieve information about firewall rule: %s", d.Id()) config := meta.(*Config) @@ -191,7 +191,7 @@ func resourceFirewallRuleRead(d *schema.ResourceData, meta interface{}) error { return nil } -func resourceFirewallRuleUpdate(d *schema.ResourceData, meta interface{}) error { +func resourceFWRuleV1Update(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { @@ -241,7 +241,7 @@ func resourceFirewallRuleUpdate(d *schema.ResourceData, meta interface{}) error return rules.Update(networkingClient, d.Id(), opts).Err } -func resourceFirewallRuleDelete(d *schema.ResourceData, meta interface{}) error { +func resourceFWRuleV1Delete(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Destroy firewall rule: %s", d.Id()) config := meta.(*Config) diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go b/builtin/providers/openstack/resource_openstack_fw_rule_v1_test.go similarity index 86% rename from builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go rename to builtin/providers/openstack/resource_openstack_fw_rule_v1_test.go index 748177375..ba96bb8b1 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v1_test.go @@ -12,17 +12,17 @@ import ( "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules" ) -func TestAccOpenstackFirewallRule(t *testing.T) { +func TestAccFWRuleV1(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: testAccCheckOpenstackFirewallRuleDestroy, + CheckDestroy: testAccCheckFWRuleV1Destroy, Steps: []resource.TestStep{ resource.TestStep{ Config: testFirewallRuleMinimalConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckFirewallRuleExists( - "openstack_fw_rule_v2.accept_test_minimal", + testAccCheckFWRuleV1Exists( + "openstack_fw_rule_v1.accept_test_minimal", &rules.Rule{ Protocol: "udp", Action: "deny", @@ -34,8 +34,8 @@ func TestAccOpenstackFirewallRule(t *testing.T) { resource.TestStep{ Config: testFirewallRuleConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckFirewallRuleExists( - "openstack_fw_rule_v2.accept_test", + testAccCheckFWRuleV1Exists( + "openstack_fw_rule_v1.accept_test", &rules.Rule{ Name: "accept_test", Protocol: "udp", @@ -53,8 +53,8 @@ func TestAccOpenstackFirewallRule(t *testing.T) { resource.TestStep{ Config: testFirewallRuleUpdateAllFieldsConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckFirewallRuleExists( - "openstack_fw_rule_v2.accept_test", + testAccCheckFWRuleV1Exists( + "openstack_fw_rule_v1.accept_test", &rules.Rule{ Name: "accept_test_updated_2", Protocol: "tcp", @@ -73,7 +73,7 @@ func TestAccOpenstackFirewallRule(t *testing.T) { }) } -func testAccCheckOpenstackFirewallRuleDestroy(s *terraform.State) error { +func testAccCheckFWRuleV1Destroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) networkingClient, err := config.networkingV2Client(OS_REGION_NAME) @@ -96,7 +96,7 @@ func testAccCheckOpenstackFirewallRuleDestroy(s *terraform.State) error { return nil } -func testAccCheckFirewallRuleExists(n string, expected *rules.Rule) resource.TestCheckFunc { +func testAccCheckFWRuleV1Exists(n string, expected *rules.Rule) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -148,14 +148,14 @@ func testAccCheckFirewallRuleExists(n string, expected *rules.Rule) resource.Tes } const testFirewallRuleMinimalConfig = ` -resource "openstack_fw_rule_v2" "accept_test_minimal" { +resource "openstack_fw_rule_v1" "accept_test_minimal" { protocol = "udp" action = "deny" } ` const testFirewallRuleConfig = ` -resource "openstack_fw_rule_v2" "accept_test" { +resource "openstack_fw_rule_v1" "accept_test" { name = "accept_test" description = "Terraform accept test" protocol = "udp" @@ -170,7 +170,7 @@ resource "openstack_fw_rule_v2" "accept_test" { ` const testFirewallRuleUpdateAllFieldsConfig = ` -resource "openstack_fw_rule_v2" "accept_test" { +resource "openstack_fw_rule_v1" "accept_test" { name = "accept_test_updated_2" description = "Terraform accept test updated" protocol = "tcp" From bdeca31731ef0890c5cf162083185ae037d1b4dd Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Thu, 19 Feb 2015 23:31:19 +0100 Subject: [PATCH 267/295] remove boilerplate code using CheckDeleted --- .../openstack/resource_openstack_fw_firewall_v1.go | 12 ++---------- .../openstack/resource_openstack_fw_policy_v1.go | 10 +--------- .../openstack/resource_openstack_fw_rule_v1.go | 11 +---------- 3 files changed, 4 insertions(+), 29 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go index 21c93d33d..732b41622 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go @@ -105,17 +105,9 @@ func resourceFWFirewallV1Read(d *schema.ResourceData, meta interface{}) error { } firewall, err := firewalls.Get(networkingClient, d.Id()).Extract() - if err != nil { - httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) - if !ok { - return err - } - if httpError.Actual == 404 { - d.SetId("") - return nil - } - return err + if err != nil { + return CheckDeleted(d, err, "LB pool") } if t, exists := d.GetOk("name"); exists && t != "" { diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v1.go b/builtin/providers/openstack/resource_openstack_fw_policy_v1.go index e9fc1645b..7837e5a43 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v1.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v1.go @@ -119,15 +119,7 @@ func resourceFWPolicyV1Read(d *schema.ResourceData, meta interface{}) error { policy, err := policies.Get(networkingClient, d.Id()).Extract() if err != nil { - httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) - if !ok { - return err - } - if httpError.Actual == 404 { - d.SetId("") - return nil - } - return err + return CheckDeleted(d, err, "LB pool") } if t, exists := d.GetOk("name"); exists && t != "" { diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v1.go b/builtin/providers/openstack/resource_openstack_fw_rule_v1.go index bd418e9b8..607d02b21 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v1.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v1.go @@ -5,7 +5,6 @@ import ( "log" "github.com/hashicorp/terraform/helper/schema" - "github.com/rackspace/gophercloud" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/policies" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/fwaas/rules" ) @@ -126,15 +125,7 @@ func resourceFWRuleV1Read(d *schema.ResourceData, meta interface{}) error { rule, err := rules.Get(networkingClient, d.Id()).Extract() if err != nil { - httpError, ok := err.(*gophercloud.UnexpectedResponseCodeError) - if !ok { - return err - } - if httpError.Actual == 404 { - d.SetId("") - return nil - } - return err + return CheckDeleted(d, err, "LB pool") } d.Set("protocol", rule.Protocol) From 83160acf69c4752d86fd8fd15b34a1035a1fa5f9 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Thu, 19 Feb 2015 23:44:49 +0100 Subject: [PATCH 268/295] Return Read call result in Create & Update --- .../openstack/resource_openstack_fw_firewall_v1.go | 9 +++++++-- .../openstack/resource_openstack_fw_policy_v1.go | 9 +++++++-- .../providers/openstack/resource_openstack_fw_rule_v1.go | 9 +++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go index 732b41622..9d5764357 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go @@ -92,7 +92,7 @@ func resourceFWFirewallV1Create(d *schema.ResourceData, meta interface{}) error d.SetId(firewall.ID) - return nil + return resourceFWFirewallV1Read(d, meta) } func resourceFWFirewallV1Read(d *schema.ResourceData, meta interface{}) error { @@ -183,7 +183,12 @@ func resourceFWFirewallV1Update(d *schema.ResourceData, meta interface{}) error _, err = stateConf.WaitForState() - return firewalls.Update(networkingClient, d.Id(), opts).Err + err = firewalls.Update(networkingClient, d.Id(), opts).Err + if err != nil { + return err + } + + return resourceFWFirewallV1Read(d, meta) } func resourceFWFirewallV1Delete(d *schema.ResourceData, meta interface{}) error { diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v1.go b/builtin/providers/openstack/resource_openstack_fw_policy_v1.go index 7837e5a43..815059752 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v1.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v1.go @@ -104,7 +104,7 @@ func resourceFWPolicyV1Create(d *schema.ResourceData, meta interface{}) error { d.SetId(policy.ID) - return nil + return resourceFWPolicyV1Read(d, meta) } func resourceFWPolicyV1Read(d *schema.ResourceData, meta interface{}) error { @@ -188,7 +188,12 @@ func resourceFWPolicyV1Update(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Updating firewall policy with id %s: %#v", d.Id(), opts) - return policies.Update(networkingClient, d.Id(), opts).Err + err = policies.Update(networkingClient, d.Id(), opts).Err + if err != nil { + return err + } + + return resourceFWPolicyV1Read(d, meta) } func resourceFWPolicyV1Delete(d *schema.ResourceData, meta interface{}) error { diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v1.go b/builtin/providers/openstack/resource_openstack_fw_rule_v1.go index 607d02b21..f4dbb2fc7 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v1.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v1.go @@ -110,7 +110,7 @@ func resourceFWRuleV1Create(d *schema.ResourceData, meta interface{}) error { d.SetId(rule.ID) - return nil + return resourceFWRuleV1Read(d, meta) } func resourceFWRuleV1Read(d *schema.ResourceData, meta interface{}) error { @@ -229,7 +229,12 @@ func resourceFWRuleV1Update(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Updating firewall rules: %#v", opts) - return rules.Update(networkingClient, d.Id(), opts).Err + err = rules.Update(networkingClient, d.Id(), opts).Err + if err != nil { + return err + } + + return resourceFWRuleV1Read(d, meta) } func resourceFWRuleV1Delete(d *schema.ResourceData, meta interface{}) error { From ba880b136bee77159ab98bde438ed61d76ec3949 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Thu, 19 Feb 2015 23:53:30 +0100 Subject: [PATCH 269/295] Code clean-up --- .../providers/openstack/resource_openstack_fw_firewall_v1.go | 3 +-- builtin/providers/openstack/resource_openstack_fw_policy_v1.go | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go index 9d5764357..16b2014a7 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go @@ -1,7 +1,6 @@ package openstack import ( - "errors" "fmt" "log" "time" @@ -258,7 +257,7 @@ func waitForFirewallDeletion(networkingClient *gophercloud.ServiceClient, id str log.Printf("[DEBUG] Firewall %s is actually deleted", id) return "", "DELETED", nil } - return nil, "", errors.New(fmt.Sprintf("Unexpected status code %d", httpStatus.Actual)) + return nil, "", fmt.Errorf("Unexpected status code %d", httpStatus.Actual) } log.Printf("[DEBUG] Firewall %s deletion is pending", id) diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v1.go b/builtin/providers/openstack/resource_openstack_fw_policy_v1.go index 815059752..e58b994fc 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v1.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v1.go @@ -62,9 +62,6 @@ func resourceFWPolicyV1() *schema.Resource { func resourceFWPolicyV1Create(d *schema.ResourceData, meta interface{}) error { - // TODO To remove - time.Sleep(time.Second * 5) - config := meta.(*Config) networkingClient, err := config.networkingV2Client(d.Get("region").(string)) if err != nil { From f5feb7fbbb4f8db0b7ae88e17e8aad27d7c5dc8a Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Wed, 4 Mar 2015 04:41:15 +0000 Subject: [PATCH 270/295] Allows "self" to be discovered and recorded correctly. --- .../resource_openstack_compute_secgroup_v2.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index cf00f7f1b..3860dc69c 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -72,6 +72,7 @@ func resourceComputeSecGroupV2() *schema.Resource { "self": &schema.Schema{ Type: schema.TypeBool, Optional: true, + Default: false, ForceNew: false, }, }, @@ -126,8 +127,16 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err d.Set("region", d.Get("region").(string)) d.Set("name", sg.Name) d.Set("description", sg.Description) - log.Printf("[DEBUG] rulesToMap(sg.Rules): %+v", rulesToMap(sg.Rules)) - d.Set("rule", rulesToMap(sg.Rules)) + rtm := rulesToMap(sg.Rules) + for _, v := range rtm { + if v["group"] == d.Get("name") { + v["self"] = "1" + } else { + v["self"] = "0" + } + } + log.Printf("[DEBUG] rulesToMap(sg.Rules): %+v", rtm) + d.Set("rule", rtm) return nil } @@ -267,6 +276,7 @@ func rulesToMap(sgrs []secgroups.Rule) []map[string]interface{} { "to_port": sgr.ToPort, "ip_protocol": sgr.IPProtocol, "cidr": sgr.IPRange.CIDR, + "group": sgr.Group.Name, } } return sgrMap From f011462e30ff9be274cc54872701fba67e42e3e7 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Fri, 13 Mar 2015 22:24:13 +0000 Subject: [PATCH 271/295] Volume Pending States This commit adds pending states for volume attachment, detachment, and deletion. --- .../openstack/resource_openstack_blockstorage_volume_v1.go | 4 ++-- .../openstack/resource_openstack_compute_instance_v2.go | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go index eefd75f7e..598640b82 100644 --- a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go +++ b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go @@ -264,7 +264,7 @@ func resourceBlockStorageVolumeV1Delete(d *schema.ResourceData, meta interface{} } stateConf := &resource.StateChangeConf{ - Pending: []string{"in-use"}, + Pending: []string{"in-use", "attaching"}, Target: "available", Refresh: VolumeV1StateRefreshFunc(blockStorageClient, d.Id()), Timeout: 10 * time.Minute, @@ -290,7 +290,7 @@ func resourceBlockStorageVolumeV1Delete(d *schema.ResourceData, meta interface{} log.Printf("[DEBUG] Waiting for volume (%s) to delete", d.Id()) stateConf := &resource.StateChangeConf{ - Pending: []string{"deleting"}, + Pending: []string{"deleting", "available"}, Target: "deleted", Refresh: VolumeV1StateRefreshFunc(blockStorageClient, d.Id()), Timeout: 10 * time.Minute, diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 348806931..90b64ed1d 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -944,6 +944,7 @@ func attachVolumesToInstance(computeClient *gophercloud.ServiceClient, blockClie } stateConf := &resource.StateChangeConf{ + Pending: []string{"attaching", "available"}, Target: "in-use", Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), Timeout: 30 * time.Minute, @@ -972,6 +973,7 @@ func detachVolumesFromInstance(computeClient *gophercloud.ServiceClient, blockCl } stateConf := &resource.StateChangeConf{ + Pending: []string{"detaching", "in-use"}, Target: "available", Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), Timeout: 30 * time.Minute, From 2e37784065dac7b024618f887c7966835efe5b22 Mon Sep 17 00:00:00 2001 From: Julien Vey Date: Tue, 24 Mar 2015 13:59:55 +0100 Subject: [PATCH 272/295] Fix general comments by @phinze --- ...source_openstack_blockstorage_volume_v1.go | 47 +++------------- ...esource_openstack_compute_floatingip_v2.go | 9 +-- .../resource_openstack_compute_instance_v2.go | 6 +- .../resource_openstack_compute_keypair_v2.go | 3 +- .../resource_openstack_compute_secgroup_v2.go | 2 +- .../resource_openstack_fw_firewall_v1.go | 34 ++---------- .../resource_openstack_fw_policy_v1.go | 37 ++----------- .../resource_openstack_fw_rule_v1.go | 55 +++---------------- .../resource_openstack_lb_monitor_v1.go | 38 ++----------- .../resource_openstack_lb_pool_v1.go | 11 +--- .../openstack/resource_openstack_lb_vip_v1.go | 3 +- ...urce_openstack_networking_floatingip_v2.go | 8 ++- ...esource_openstack_networking_network_v2.go | 31 ++--------- ...penstack_networking_router_interface_v2.go | 4 +- ...resource_openstack_networking_router_v2.go | 31 ++--------- ...resource_openstack_networking_subnet_v2.go | 46 +++------------- ...ce_openstack_objectstorage_container_v1.go | 2 +- 17 files changed, 67 insertions(+), 300 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go index 598640b82..dc2638590 100644 --- a/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go +++ b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v1.go @@ -111,7 +111,7 @@ func resourceBlockStorageVolumeV1Create(d *schema.ResourceData, meta interface{} Metadata: resourceContainerMetadataV2(d), } - log.Printf("[INFO] Requesting volume creation") + log.Printf("[DEBUG] Create Options: %#v", createOpts) v, err := volumes.Create(blockStorageClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack volume: %s", err) @@ -156,48 +156,15 @@ func resourceBlockStorageVolumeV1Read(d *schema.ResourceData, meta interface{}) return CheckDeleted(d, err, "volume") } - log.Printf("\n\ngot volume: %+v\n\n", v) - log.Printf("[DEBUG] Retreived volume %s: %+v", d.Id(), v) - d.Set("region", d.Get("region").(string)) d.Set("size", v.Size) - - if t, exists := d.GetOk("description"); exists && t != "" { - d.Set("description", v.Description) - } else { - d.Set("description", "") - } - - if t, exists := d.GetOk("name"); exists && t != "" { - d.Set("name", v.Name) - } else { - d.Set("name", "") - } - - if t, exists := d.GetOk("snapshot_id"); exists && t != "" { - d.Set("snapshot_id", v.SnapshotID) - } else { - d.Set("snapshot_id", "") - } - - if t, exists := d.GetOk("source_vol_id"); exists && t != "" { - d.Set("source_vol_id", v.SourceVolID) - } else { - d.Set("source_vol_id", "") - } - - if t, exists := d.GetOk("volume_type"); exists && t != "" { - d.Set("volume_type", v.VolumeType) - } else { - d.Set("volume_type", "") - } - - if t, exists := d.GetOk("metadata"); exists && t != "" { - d.Set("metadata", v.Metadata) - } else { - d.Set("metadata", "") - } + d.Set("description", v.Description) + d.Set("name", v.Name) + d.Set("snapshot_id", v.SnapshotID) + d.Set("source_vol_id", v.SourceVolID) + d.Set("volume_type", v.VolumeType) + d.Set("metadata", v.Metadata) if len(v.Attachments) > 0 { attachments := make([]map[string]interface{}, len(v.Attachments)) diff --git a/builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go index 3501fe282..bb2facc4e 100644 --- a/builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2.go @@ -30,12 +30,6 @@ func resourceComputeFloatingIPV2() *schema.Resource { DefaultFunc: envDefaultFunc("OS_POOL_NAME"), }, - // exported - "id": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - }, - "address": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -64,6 +58,7 @@ func resourceComputeFloatingIPV2Create(d *schema.ResourceData, meta interface{}) createOpts := &floatingip.CreateOpts{ Pool: d.Get("pool").(string), } + log.Printf("[DEBUG] Create Options: %#v", createOpts) newFip, err := floatingip.Create(computeClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating Floating IP: %s", err) @@ -88,8 +83,6 @@ func resourceComputeFloatingIPV2Read(d *schema.ResourceData, meta interface{}) e log.Printf("[DEBUG] Retrieved Floating IP %s: %+v", d.Id(), fip) - d.Set("id", d.Id()) - d.Set("region", d.Get("region").(string)) d.Set("pool", fip.Pool) d.Set("instance_id", fip.InstanceID) d.Set("address", fip.IP) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 90b64ed1d..63508ffa7 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -241,7 +241,7 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e } } - log.Printf("[INFO] Requesting instance creation") + log.Printf("[DEBUG] Create Options: %#v", createOpts) server, err := servers.Create(computeClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack server: %s", err) @@ -320,7 +320,6 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server) - d.Set("region", d.Get("region").(string)) d.Set("name", server.Name) d.Set("access_ip_v4", server.AccessIPv4) d.Set("access_ip_v6", server.AccessIPv6) @@ -360,7 +359,7 @@ func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) err publicAddresses := publicAddressesRaw.([]interface{}) for _, paRaw := range publicAddresses { pa := paRaw.(map[string]interface{}) - if pa["version"].(float64) == 4 { + if pa["version"].(float64) == 6 { hostv6 = fmt.Sprintf("[%s]", pa["addr"].(string)) break } @@ -594,6 +593,7 @@ func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) e resizeOpts := &servers.ResizeOpts{ FlavorRef: flavorId, } + log.Printf("[DEBUG] Resize configuration: %#v", resizeOpts) err = servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr() if err != nil { return fmt.Errorf("Error resizing OpenStack server: %s", err) diff --git a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go index 9c1417412..db6bed5b2 100644 --- a/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_keypair_v2.go @@ -2,6 +2,7 @@ package openstack import ( "fmt" + "log" "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" @@ -46,6 +47,7 @@ func resourceComputeKeypairV2Create(d *schema.ResourceData, meta interface{}) er PublicKey: d.Get("public_key").(string), } + log.Printf("[DEBUG] Create Options: %#v", createOpts) kp, err := keypairs.Create(computeClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack keypair: %s", err) @@ -68,7 +70,6 @@ func resourceComputeKeypairV2Read(d *schema.ResourceData, meta interface{}) erro return CheckDeleted(d, err, "keypair") } - d.Set("region", d.Get("region").(string)) d.Set("name", kp.Name) d.Set("public_key", kp.PublicKey) diff --git a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go index 3860dc69c..ca646d77d 100644 --- a/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_secgroup_v2.go @@ -94,6 +94,7 @@ func resourceComputeSecGroupV2Create(d *schema.ResourceData, meta interface{}) e Description: d.Get("description").(string), } + log.Printf("[DEBUG] Create Options: %#v", createOpts) sg, err := secgroups.Create(computeClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack security group: %s", err) @@ -124,7 +125,6 @@ func resourceComputeSecGroupV2Read(d *schema.ResourceData, meta interface{}) err return CheckDeleted(d, err, "security group") } - d.Set("region", d.Get("region").(string)) d.Set("name", sg.Name) d.Set("description", sg.Description) rtm := rulesToMap(sg.Rules) diff --git a/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go b/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go index 16b2014a7..8505ac3b3 100644 --- a/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go +++ b/builtin/providers/openstack/resource_openstack_fw_firewall_v1.go @@ -109,35 +109,11 @@ func resourceFWFirewallV1Read(d *schema.ResourceData, meta interface{}) error { return CheckDeleted(d, err, "LB pool") } - if t, exists := d.GetOk("name"); exists && t != "" { - d.Set("name", firewall.Name) - } else { - d.Set("name", "") - } - - if t, exists := d.GetOk("description"); exists && t != "" { - d.Set("description", firewall.Description) - } else { - d.Set("description", "") - } - - if t, exists := d.GetOk("policy_id"); exists && t != "" { - d.Set("policy_id", firewall.PolicyID) - } else { - d.Set("policy_id", "") - } - - if t, exists := d.GetOk("admin_state_up"); exists && t != "" { - d.Set("admin_state_up", firewall.AdminStateUp) - } else { - d.Set("admin_state_up", "") - } - - if t, exists := d.GetOk("tenant_id"); exists && t != "" { - d.Set("tenant_id", firewall.TenantID) - } else { - d.Set("tenant_id", "") - } + d.Set("name", firewall.Name) + d.Set("description", firewall.Description) + d.Set("policy_id", firewall.PolicyID) + d.Set("admin_state_up", firewall.AdminStateUp) + d.Set("tenant_id", firewall.TenantID) return nil } diff --git a/builtin/providers/openstack/resource_openstack_fw_policy_v1.go b/builtin/providers/openstack/resource_openstack_fw_policy_v1.go index e58b994fc..0560bfcef 100644 --- a/builtin/providers/openstack/resource_openstack_fw_policy_v1.go +++ b/builtin/providers/openstack/resource_openstack_fw_policy_v1.go @@ -97,7 +97,7 @@ func resourceFWPolicyV1Create(d *schema.ResourceData, meta interface{}) error { return err } - log.Printf("[DEBUG] Firewall policy craeted: %#v", policy) + log.Printf("[DEBUG] Firewall policy created: %#v", policy) d.SetId(policy.ID) @@ -119,36 +119,11 @@ func resourceFWPolicyV1Read(d *schema.ResourceData, meta interface{}) error { return CheckDeleted(d, err, "LB pool") } - if t, exists := d.GetOk("name"); exists && t != "" { - d.Set("name", policy.Name) - } else { - d.Set("name", "") - } - - if t, exists := d.GetOk("description"); exists && t != "" { - d.Set("description", policy.Description) - } else { - d.Set("description", "") - } - - if t, exists := d.GetOk("shared"); exists && t != "" { - d.Set("shared", policy.Shared) - } else { - d.Set("shared", "") - } - - if t, exists := d.GetOk("audited"); exists && t != "" { - d.Set("audited", policy.Audited) - } else { - d.Set("audited", "") - } - - if t, exists := d.GetOk("tenant_id"); exists && t != "" { - d.Set("tenant_id", policy.TenantID) - } else { - d.Set("tenant_id", "") - } - + d.Set("name", policy.Name) + d.Set("description", policy.Description) + d.Set("shared", policy.Shared) + d.Set("audited", policy.Audited) + d.Set("tenant_id", policy.TenantID) return nil } diff --git a/builtin/providers/openstack/resource_openstack_fw_rule_v1.go b/builtin/providers/openstack/resource_openstack_fw_rule_v1.go index f4dbb2fc7..f0f5affcc 100644 --- a/builtin/providers/openstack/resource_openstack_fw_rule_v1.go +++ b/builtin/providers/openstack/resource_openstack_fw_rule_v1.go @@ -131,53 +131,14 @@ func resourceFWRuleV1Read(d *schema.ResourceData, meta interface{}) error { d.Set("protocol", rule.Protocol) d.Set("action", rule.Action) - if t, exists := d.GetOk("name"); exists && t != "" { - d.Set("name", rule.Name) - } else { - d.Set("name", "") - } - - if t, exists := d.GetOk("description"); exists && t != "" { - d.Set("description", rule.Description) - } else { - d.Set("description", "") - } - - if t, exists := d.GetOk("ip_version"); exists && t != "" { - d.Set("ip_version", rule.IPVersion) - } else { - d.Set("ip_version", "") - } - - if t, exists := d.GetOk("source_ip_address"); exists && t != "" { - d.Set("source_ip_address", rule.SourceIPAddress) - } else { - d.Set("source_ip_address", "") - } - - if t, exists := d.GetOk("destination_ip_address"); exists && t != "" { - d.Set("destination_ip_address", rule.DestinationIPAddress) - } else { - d.Set("destination_ip_address", "") - } - - if t, exists := d.GetOk("source_port"); exists && t != "" { - d.Set("source_port", rule.SourcePort) - } else { - d.Set("source_port", "") - } - - if t, exists := d.GetOk("destination_port"); exists && t != "" { - d.Set("destination_port", rule.DestinationPort) - } else { - d.Set("destination_port", "") - } - - if t, exists := d.GetOk("enabled"); exists && t != "" { - d.Set("enabled", rule.Enabled) - } else { - d.Set("enabled", "") - } + d.Set("name", rule.Name) + d.Set("description", rule.Description) + d.Set("ip_version", rule.IPVersion) + d.Set("source_ip_address", rule.SourceIPAddress) + d.Set("destination_ip_address", rule.DestinationIPAddress) + d.Set("source_port", rule.SourcePort) + d.Set("destination_port", rule.DestinationPort) + d.Set("enabled", rule.Enabled) return nil } diff --git a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go index 0761ec1f4..cdfd54ccc 100644 --- a/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_monitor_v1.go @@ -99,7 +99,7 @@ func resourceLBMonitorV1Create(d *schema.ResourceData, meta interface{}) error { createOpts.AdminStateUp = &asu } - log.Printf("[INFO] Requesting lb monitor creation") + log.Printf("[DEBUG] Create Options: %#v", createOpts) m, err := monitors.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack LB Monitor: %s", err) @@ -125,41 +125,15 @@ func resourceLBMonitorV1Read(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Retreived OpenStack LB Monitor %s: %+v", d.Id(), m) - d.Set("region", d.Get("region").(string)) d.Set("type", m.Type) d.Set("delay", m.Delay) d.Set("timeout", m.Timeout) d.Set("max_retries", m.MaxRetries) - - if t, exists := d.GetOk("tenant_id"); exists && t != "" { - d.Set("tenant_id", m.TenantID) - } else { - d.Set("tenant_id", "") - } - - if t, exists := d.GetOk("url_path"); exists && t != "" { - d.Set("url_path", m.URLPath) - } else { - d.Set("url_path", "") - } - - if t, exists := d.GetOk("http_method"); exists && t != "" { - d.Set("http_method", m.HTTPMethod) - } else { - d.Set("http_method", "") - } - - if t, exists := d.GetOk("expected_codes"); exists && t != "" { - d.Set("expected_codes", m.ExpectedCodes) - } else { - d.Set("expected_codes", "") - } - - if t, exists := d.GetOk("admin_state_up"); exists && t != "" { - d.Set("admin_state_up", strconv.FormatBool(m.AdminStateUp)) - } else { - d.Set("admin_state_up", "") - } + d.Set("tenant_id", m.TenantID) + d.Set("url_path", m.URLPath) + d.Set("http_method", m.HTTPMethod) + d.Set("expected_codes", m.ExpectedCodes) + d.Set("admin_state_up", strconv.FormatBool(m.AdminStateUp)) return nil } diff --git a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go index 7e3abdbf7..6b69f2fac 100644 --- a/builtin/providers/openstack/resource_openstack_lb_pool_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_pool_v1.go @@ -115,7 +115,7 @@ func resourceLBPoolV1Create(d *schema.ResourceData, meta interface{}) error { TenantID: d.Get("tenant_id").(string), } - log.Printf("[INFO] Requesting lb pool creation") + log.Printf("[DEBUG] Create Options: %#v", createOpts) p, err := pools.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack LB pool: %s", err) @@ -159,18 +159,11 @@ func resourceLBPoolV1Read(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Retreived OpenStack LB Pool %s: %+v", d.Id(), p) - d.Set("region", d.Get("region").(string)) d.Set("name", p.Name) d.Set("protocol", p.Protocol) d.Set("subnet_id", p.SubnetID) d.Set("lb_method", p.LBMethod) - - if t, exists := d.GetOk("tenant_id"); exists && t != "" { - d.Set("tenant_id", p.TenantID) - } else { - d.Set("tenant_id", "") - } - + d.Set("tenant_id", p.TenantID) d.Set("monitor_ids", p.MonitorIDs) d.Set("member_ids", p.MemberIDs) diff --git a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go index 6181bc8a5..bd2ae135e 100644 --- a/builtin/providers/openstack/resource_openstack_lb_vip_v1.go +++ b/builtin/providers/openstack/resource_openstack_lb_vip_v1.go @@ -112,7 +112,7 @@ func resourceLBVipV1Create(d *schema.ResourceData, meta interface{}) error { createOpts.AdminStateUp = &asu } - log.Printf("[INFO] Requesting lb vip creation") + log.Printf("[DEBUG] Create Options: %#v", createOpts) p, err := vips.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack LB VIP: %s", err) @@ -138,7 +138,6 @@ func resourceLBVipV1Read(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] Retreived OpenStack LB VIP %s: %+v", d.Id(), p) - d.Set("region", d.Get("region").(string)) d.Set("name", p.Name) d.Set("subnet_id", p.SubnetID) d.Set("protocol", p.Protocol) diff --git a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go index fb4965768..cbc430764 100644 --- a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go @@ -2,6 +2,7 @@ package openstack import ( "fmt" + "log" "github.com/hashicorp/terraform/helper/schema" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" @@ -49,9 +50,11 @@ func resourceNetworkFloatingIPV2Create(d *schema.ResourceData, meta interface{}) if len(poolID) == 0 { return fmt.Errorf("No network found with name: %s", d.Get("pool").(string)) } - floatingIP, err := floatingips.Create(networkClient, floatingips.CreateOpts{ + createOpts := floatingips.CreateOpts{ FloatingNetworkID: poolID, - }).Extract() + } + log.Printf("[DEBUG] Create Options: %#v", createOpts) + floatingIP, err := floatingips.Create(networkClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error allocating floating IP: %s", err) } @@ -73,7 +76,6 @@ func resourceNetworkFloatingIPV2Read(d *schema.ResourceData, meta interface{}) e return CheckDeleted(d, err, "floating IP") } - d.Set("region", d.Get("region").(string)) d.Set("address", floatingIP.FloatingIP) poolName, err := getNetworkName(d, meta, floatingIP.FloatingNetworkID) if err != nil { diff --git a/builtin/providers/openstack/resource_openstack_networking_network_v2.go b/builtin/providers/openstack/resource_openstack_networking_network_v2.go index bf556f418..2ac4ab94e 100644 --- a/builtin/providers/openstack/resource_openstack_networking_network_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_network_v2.go @@ -77,7 +77,7 @@ func resourceNetworkingNetworkV2Create(d *schema.ResourceData, meta interface{}) createOpts.Shared = &shared } - log.Printf("[INFO] Requesting network creation") + log.Printf("[DEBUG] Create Options: %#v", createOpts) n, err := networks.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack Neutron network: %s", err) @@ -103,31 +103,10 @@ func resourceNetworkingNetworkV2Read(d *schema.ResourceData, meta interface{}) e log.Printf("[DEBUG] Retreived Network %s: %+v", d.Id(), n) - d.Set("region", d.Get("region").(string)) - - if t, exists := d.GetOk("name"); exists && t != "" { - d.Set("name", n.Name) - } else { - d.Set("name", "") - } - - if t, exists := d.GetOk("admin_state_up"); exists && t != "" { - d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp)) - } else { - d.Set("admin_state_up", "") - } - - if t, exists := d.GetOk("shared"); exists && t != "" { - d.Set("shared", strconv.FormatBool(n.Shared)) - } else { - d.Set("shared", "") - } - - if t, exists := d.GetOk("tenant_id"); exists && t != "" { - d.Set("tenant_id", n.TenantID) - } else { - d.Set("tenant_id", "") - } + d.Set("name", n.Name) + d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp)) + d.Set("shared", strconv.FormatBool(n.Shared)) + d.Set("tenant_id", n.TenantID) return nil } diff --git a/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go index 492c14d30..e67ff6f58 100644 --- a/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_router_interface_v2.go @@ -48,7 +48,7 @@ func resourceNetworkingRouterInterfaceV2Create(d *schema.ResourceData, meta inte SubnetID: d.Get("subnet_id").(string), } - log.Printf("[INFO] Requesting router interface creation") + log.Printf("[DEBUG] Create Options: %#v", createOpts) n, err := routers.AddInterface(networkingClient, d.Get("router_id").(string), createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack Neutron router interface: %s", err) @@ -83,8 +83,6 @@ func resourceNetworkingRouterInterfaceV2Read(d *schema.ResourceData, meta interf log.Printf("[DEBUG] Retreived Router Interface %s: %+v", d.Id(), n) - d.Set("region", d.Get("region").(string)) - return nil } diff --git a/builtin/providers/openstack/resource_openstack_networking_router_v2.go b/builtin/providers/openstack/resource_openstack_networking_router_v2.go index cb235a88b..3b6df4816 100644 --- a/builtin/providers/openstack/resource_openstack_networking_router_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_router_v2.go @@ -77,7 +77,7 @@ func resourceNetworkingRouterV2Create(d *schema.ResourceData, meta interface{}) createOpts.GatewayInfo = &gatewayInfo } - log.Printf("[INFO] Requesting router creation") + log.Printf("[DEBUG] Create Options: %#v", createOpts) n, err := routers.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack Neutron router: %s", err) @@ -112,31 +112,10 @@ func resourceNetworkingRouterV2Read(d *schema.ResourceData, meta interface{}) er log.Printf("[DEBUG] Retreived Router %s: %+v", d.Id(), n) - d.Set("region", d.Get("region").(string)) - - if t, exists := d.GetOk("name"); exists && t != "" { - d.Set("name", n.Name) - } else { - d.Set("name", "") - } - - if t, exists := d.GetOk("admin_state_up"); exists && t != "" { - d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp)) - } else { - d.Set("admin_state_up", "") - } - - if t, exists := d.GetOk("tenant_id"); exists && t != "" { - d.Set("tenant_id", n.TenantID) - } else { - d.Set("tenant_id", "") - } - - if t, exists := d.GetOk("external_gateway"); exists && t != "" { - d.Set("external_gateway", n.GatewayInfo.NetworkID) - } else { - d.Set("external_gateway", "") - } + d.Set("name", n.Name) + d.Set("admin_state_up", strconv.FormatBool(n.AdminStateUp)) + d.Set("tenant_id", n.TenantID) + d.Set("external_gateway", n.GatewayInfo.NetworkID) return nil } diff --git a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go index 8462611df..2f898bb4c 100644 --- a/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_subnet_v2.go @@ -134,7 +134,7 @@ func resourceNetworkingSubnetV2Create(d *schema.ResourceData, meta interface{}) createOpts.EnableDHCP = &ed } - log.Printf("[INFO] Requesting subnet creation") + log.Printf("[DEBUG] Create Options: %#v", createOpts) s, err := subnets.Create(networkingClient, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack Neutron subnet: %s", err) @@ -160,46 +160,16 @@ func resourceNetworkingSubnetV2Read(d *schema.ResourceData, meta interface{}) er log.Printf("[DEBUG] Retreived Subnet %s: %+v", d.Id(), s) - d.Set("region", d.Get("region").(string)) d.Set("newtork_id", s.NetworkID) d.Set("cidr", s.CIDR) d.Set("ip_version", s.IPVersion) - - if t, exists := d.GetOk("name"); exists && t != "" { - d.Set("name", s.Name) - } else { - d.Set("name", "") - } - - if t, exists := d.GetOk("tenant_id"); exists && t != "" { - d.Set("tenant_id", s.TenantID) - } else { - d.Set("tenant_id", "") - } - - if _, exists := d.GetOk("allocation_pools"); exists { - d.Set("allocation_pools", s.AllocationPools) - } - - if t, exists := d.GetOk("gateway_ip"); exists && t != "" { - d.Set("gateway_ip", s.GatewayIP) - } else { - d.Set("gateway_ip", "") - } - - if t, exists := d.GetOk("enable_dhcp"); exists && t != "" { - d.Set("enable_dhcp", strconv.FormatBool(s.EnableDHCP)) - } else { - d.Set("enable_dhcp", "") - } - - if _, exists := d.GetOk("dns_nameservers"); exists { - d.Set("dns_nameservers", s.DNSNameservers) - } - - if _, exists := d.GetOk("host_routes"); exists { - d.Set("host_routes", s.HostRoutes) - } + d.Set("name", s.Name) + d.Set("tenant_id", s.TenantID) + d.Set("allocation_pools", s.AllocationPools) + d.Set("gateway_ip", s.GatewayIP) + d.Set("enable_dhcp", strconv.FormatBool(s.EnableDHCP)) + d.Set("dns_nameservers", s.DNSNameservers) + d.Set("host_routes", s.HostRoutes) return nil } diff --git a/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go b/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go index 692ed2e46..31666a356 100644 --- a/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go +++ b/builtin/providers/openstack/resource_openstack_objectstorage_container_v1.go @@ -79,7 +79,7 @@ func resourceObjectStorageContainerV1Create(d *schema.ResourceData, meta interfa Metadata: resourceContainerMetadataV2(d), } - log.Printf("[INFO] Requesting container creation") + log.Printf("[DEBUG] Create Options: %#v", createOpts) _, err = containers.Create(objectStorageClient, cn, createOpts).Extract() if err != nil { return fmt.Errorf("Error creating OpenStack container: %s", err) From 0092946f748f77770e7807d16f6c3e48ae6b14d1 Mon Sep 17 00:00:00 2001 From: Chris Buben Date: Fri, 20 Mar 2015 18:55:42 -0400 Subject: [PATCH 273/295] user_data support Mostly stolen from: https://github.com/jtopjian/terraform-provider-openstack/blob/master/openstack/resource_openstack_instance.go --- .../resource_openstack_compute_instance_v2.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 63508ffa7..5a4c16c7e 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -2,6 +2,8 @@ package openstack import ( "bytes" + "crypto/sha1" + "encoding/hex" "fmt" "log" "time" @@ -75,6 +77,21 @@ func resourceComputeInstanceV2() *schema.Resource { Optional: true, ForceNew: false, }, + "user_data": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + // just stash the hash for state & diff comparisons + StateFunc: func(v interface{}) string { + switch v.(type) { + case string: + hash := sha1.Sum([]byte(v.(string))) + return hex.EncodeToString(hash[:]) + default: + return "" + } + }, + }, "security_groups": &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -224,6 +241,7 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e Metadata: resourceInstanceMetadataV2(d), ConfigDrive: d.Get("config_drive").(bool), AdminPass: d.Get("admin_pass").(string), + UserData: []byte(d.Get("user_data").(string)), } if keyName, ok := d.Get("key_pair").(string); ok && keyName != "" { From 97acccd3eda70f7552cecd71ea8f5916b215b694 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Tue, 24 Mar 2015 11:18:15 -0500 Subject: [PATCH 274/295] core: targeted operations Add `-target=resource` flag to core operations, allowing users to target specific resources in their infrastructure. When `-target` is used, the operation will only apply to that resource and its dependencies. The calculated dependencies are different depending on whether we're running a normal operation or a `terraform destroy`. Generally, "dependencies" refers to ancestors: resources falling _before_ the target in the graph, because their changes are required to accurately act on the target. For destroys, "dependencies" are descendents: those resources which fall _after_ the target. These resources depend on our target, which is going to be destroyed, so they should also be destroyed. --- command/apply.go | 16 +- command/apply_destroy_test.go | 90 ++++++ command/flag_kv.go | 14 + command/meta.go | 11 + command/plan.go | 7 +- command/refresh.go | 4 + .../apply-destroy-targeted/main.tf | 7 + dag/dag.go | 93 +++++- dag/dag_test.go | 62 ++++ helper/resource/testing.go | 3 +- terraform/context.go | 12 +- terraform/context_test.go | 266 ++++++++++-------- terraform/graph_builder.go | 11 + terraform/plan.go | 9 - .../transform-targets-basic/main.tf | 16 ++ .../transform-targets-destroy/main.tf | 18 ++ terraform/transform_targets.go | 77 +++++ terraform/transform_targets_test.go | 71 +++++ 18 files changed, 637 insertions(+), 150 deletions(-) create mode 100644 command/test-fixtures/apply-destroy-targeted/main.tf create mode 100644 terraform/test-fixtures/transform-targets-basic/main.tf create mode 100644 terraform/test-fixtures/transform-targets-destroy/main.tf create mode 100644 terraform/transform_targets.go create mode 100644 terraform/transform_targets_test.go diff --git a/command/apply.go b/command/apply.go index d46b71679..529d6e701 100644 --- a/command/apply.go +++ b/command/apply.go @@ -93,6 +93,7 @@ func (c *ApplyCommand) Run(args []string) int { // Build the context based on the arguments given ctx, planned, err := c.Context(contextOpts{ + Destroy: c.Destroy, Path: configPath, StatePath: c.Meta.statePath, }) @@ -140,12 +141,7 @@ func (c *ApplyCommand) Run(args []string) int { } } - var opts terraform.PlanOpts - if c.Destroy { - opts.Destroy = true - } - - if _, err := ctx.Plan(&opts); err != nil { + if _, err := ctx.Plan(); err != nil { c.Ui.Error(fmt.Sprintf( "Error creating plan: %s", err)) return 1 @@ -319,6 +315,10 @@ Options: "-state". This can be used to preserve the old state. + -target=resource Resource to target. Operation will be limited to this + resource and its dependencies. This flag can be used + multiple times. + -var 'foo=bar' Set a variable in the Terraform configuration. This flag can be set multiple times. @@ -357,6 +357,10 @@ Options: "-state". This can be used to preserve the old state. + -target=resource Resource to target. Operation will be limited to this + resource and its dependencies. This flag can be used + multiple times. + -var 'foo=bar' Set a variable in the Terraform configuration. This flag can be set multiple times. diff --git a/command/apply_destroy_test.go b/command/apply_destroy_test.go index bdc2440f0..63afb15ed 100644 --- a/command/apply_destroy_test.go +++ b/command/apply_destroy_test.go @@ -116,6 +116,96 @@ func TestApply_destroyPlan(t *testing.T) { } } +func TestApply_destroyTargeted(t *testing.T) { + originalState := &terraform.State{ + Modules: []*terraform.ModuleState{ + &terraform.ModuleState{ + Path: []string{"root"}, + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + Type: "test_instance", + Primary: &terraform.InstanceState{ + ID: "i-ab123", + }, + }, + "test_load_balancer.foo": &terraform.ResourceState{ + Type: "test_load_balancer", + Primary: &terraform.InstanceState{ + ID: "lb-abc123", + }, + }, + }, + }, + }, + } + + statePath := testStateFile(t, originalState) + + p := testProvider() + ui := new(cli.MockUi) + c := &ApplyCommand{ + Destroy: true, + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + // Run the apply command pointing to our existing state + args := []string{ + "-force", + "-target", "test_instance.foo", + "-state", statePath, + testFixturePath("apply-destroy-targeted"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Verify a new state exists + if _, err := os.Stat(statePath); err != nil { + t.Fatalf("err: %s", err) + } + + f, err := os.Open(statePath) + if err != nil { + t.Fatalf("err: %s", err) + } + defer f.Close() + + state, err := terraform.ReadState(f) + if err != nil { + t.Fatalf("err: %s", err) + } + if state == nil { + t.Fatal("state should not be nil") + } + + actualStr := strings.TrimSpace(state.String()) + expectedStr := strings.TrimSpace(testApplyDestroyStr) + if actualStr != expectedStr { + t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) + } + + // Should have a backup file + f, err = os.Open(statePath + DefaultBackupExtention) + if err != nil { + t.Fatalf("err: %s", err) + } + + backupState, err := terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + actualStr = strings.TrimSpace(backupState.String()) + expectedStr = strings.TrimSpace(originalState.String()) + if actualStr != expectedStr { + t.Fatalf("bad:\n\nactual:\n%s\n\nexpected:\nb%s", actualStr, expectedStr) + } +} + const testApplyDestroyStr = ` ` diff --git a/command/flag_kv.go b/command/flag_kv.go index fd9b57b3a..6e0198778 100644 --- a/command/flag_kv.go +++ b/command/flag_kv.go @@ -85,3 +85,17 @@ func loadKVFile(rawPath string) (map[string]string, error) { return result, nil } + +// FlagStringSlice is a flag.Value implementation for parsing targets from the +// command line, e.g. -target=aws_instance.foo -target=aws_vpc.bar + +type FlagStringSlice []string + +func (v *FlagStringSlice) String() string { + return "" +} +func (v *FlagStringSlice) Set(raw string) error { + *v = append(*v, raw) + + return nil +} diff --git a/command/meta.go b/command/meta.go index 28a01c542..b542304af 100644 --- a/command/meta.go +++ b/command/meta.go @@ -38,6 +38,9 @@ type Meta struct { input bool variables map[string]string + // Targets for this context (private) + targets []string + color bool oldUi cli.Ui @@ -126,6 +129,9 @@ func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) { m.statePath = copts.StatePath } + // Tell the context if we're in a destroy plan / apply + opts.Destroy = copts.Destroy + // Store the loaded state state, err := m.State() if err != nil { @@ -267,6 +273,7 @@ func (m *Meta) contextOpts() *terraform.ContextOpts { vs[k] = v } opts.Variables = vs + opts.Targets = m.targets opts.UIInput = m.UIInput() return &opts @@ -278,6 +285,7 @@ func (m *Meta) flagSet(n string) *flag.FlagSet { f.BoolVar(&m.input, "input", true, "input") f.Var((*FlagKV)(&m.variables), "var", "variables") f.Var((*FlagKVFile)(&m.variables), "var-file", "variable file") + f.Var((*FlagStringSlice)(&m.targets), "target", "resource to target") if m.autoKey != "" { f.Var((*FlagKVFile)(&m.autoVariables), m.autoKey, "variable file") @@ -388,4 +396,7 @@ type contextOpts struct { // GetMode is the module.GetMode to use when loading the module tree. GetMode module.GetMode + + // Set to true when running a destroy plan/apply. + Destroy bool } diff --git a/command/plan.go b/command/plan.go index 24365d185..5c884d632 100644 --- a/command/plan.go +++ b/command/plan.go @@ -53,6 +53,7 @@ func (c *PlanCommand) Run(args []string) int { } ctx, _, err := c.Context(contextOpts{ + Destroy: destroy, Path: path, StatePath: c.Meta.statePath, }) @@ -86,7 +87,7 @@ func (c *PlanCommand) Run(args []string) int { } } - plan, err := ctx.Plan(&terraform.PlanOpts{Destroy: destroy}) + plan, err := ctx.Plan() if err != nil { c.Ui.Error(fmt.Sprintf("Error running plan: %s", err)) return 1 @@ -168,6 +169,10 @@ Options: up Terraform-managed resources. By default it will use the state "terraform.tfstate" if it exists. + -target=resource Resource to target. Operation will be limited to this + resource and its dependencies. This flag can be used + multiple times. + -var 'foo=bar' Set a variable in the Terraform configuration. This flag can be set multiple times. diff --git a/command/refresh.go b/command/refresh.go index 38d630050..32e795047 100644 --- a/command/refresh.go +++ b/command/refresh.go @@ -135,6 +135,10 @@ Options: -state-out=path Path to write updated state file. By default, the "-state" path will be used. + -target=resource Resource to target. Operation will be limited to this + resource and its dependencies. This flag can be used + multiple times. + -var 'foo=bar' Set a variable in the Terraform configuration. This flag can be set multiple times. diff --git a/command/test-fixtures/apply-destroy-targeted/main.tf b/command/test-fixtures/apply-destroy-targeted/main.tf new file mode 100644 index 000000000..45ebc5b97 --- /dev/null +++ b/command/test-fixtures/apply-destroy-targeted/main.tf @@ -0,0 +1,7 @@ +resource "test_instance" "foo" { + count = 3 +} + +resource "test_load_balancer" "foo" { + instances = ["${test_instance.foo.*.id}"] +} diff --git a/dag/dag.go b/dag/dag.go index b81cb2874..0f53fb1f0 100644 --- a/dag/dag.go +++ b/dag/dag.go @@ -17,6 +17,40 @@ type AcyclicGraph struct { // WalkFunc is the callback used for walking the graph. type WalkFunc func(Vertex) error +// Returns a Set that includes every Vertex yielded by walking down from the +// provided starting Vertex v. +func (g *AcyclicGraph) Ancestors(v Vertex) (*Set, error) { + s := new(Set) + start := asVertexList(g.DownEdges(v)) + memoFunc := func(v Vertex) error { + s.Add(v) + return nil + } + + if err := g.depthFirstWalk(start, memoFunc); err != nil { + return nil, err + } + + return s, nil +} + +// Returns a Set that includes every Vertex yielded by walking up from the +// provided starting Vertex v. +func (g *AcyclicGraph) Descendents(v Vertex) (*Set, error) { + s := new(Set) + start := asVertexList(g.UpEdges(v)) + memoFunc := func(v Vertex) error { + s.Add(v) + return nil + } + + if err := g.reverseDepthFirstWalk(start, memoFunc); err != nil { + return nil, err + } + + return s, nil +} + // Root returns the root of the DAG, or an error. // // Complexity: O(V) @@ -61,15 +95,11 @@ func (g *AcyclicGraph) TransitiveReduction() { for _, u := range g.Vertices() { uTargets := g.DownEdges(u) - vs := make([]Vertex, uTargets.Len()) - for i, vRaw := range uTargets.List() { - vs[i] = vRaw.(Vertex) - } + vs := asVertexList(g.DownEdges(u)) g.depthFirstWalk(vs, func(v Vertex) error { shared := uTargets.Intersection(g.DownEdges(v)) - for _, raw := range shared.List() { - vPrime := raw.(Vertex) + for _, vPrime := range asVertexList(shared) { g.RemoveEdge(BasicEdge(u, vPrime)) } @@ -145,12 +175,10 @@ func (g *AcyclicGraph) Walk(cb WalkFunc) error { for _, v := range vertices { // Build our list of dependencies and the list of channels to // wait on until we start executing for this vertex. - depsRaw := g.DownEdges(v).List() - deps := make([]Vertex, len(depsRaw)) + deps := asVertexList(g.DownEdges(v)) depChs := make([]<-chan struct{}, len(deps)) - for i, raw := range depsRaw { - deps[i] = raw.(Vertex) - depChs[i] = vertMap[deps[i]] + for i, dep := range deps { + depChs[i] = vertMap[dep] } // Get our channel so that we can close it when we're done @@ -200,6 +228,16 @@ func (g *AcyclicGraph) Walk(cb WalkFunc) error { return errs } +// simple convenience helper for converting a dag.Set to a []Vertex +func asVertexList(s *Set) []Vertex { + rawList := s.List() + vertexList := make([]Vertex, len(rawList)) + for i, raw := range rawList { + vertexList[i] = raw.(Vertex) + } + return vertexList +} + // depthFirstWalk does a depth-first walk of the graph starting from // the vertices in start. This is not exported now but it would make sense // to export this publicly at some point. @@ -233,3 +271,36 @@ func (g *AcyclicGraph) depthFirstWalk(start []Vertex, cb WalkFunc) error { return nil } + +// reverseDepthFirstWalk does a depth-first walk _up_ the graph starting from +// the vertices in start. +func (g *AcyclicGraph) reverseDepthFirstWalk(start []Vertex, cb WalkFunc) error { + seen := make(map[Vertex]struct{}) + frontier := make([]Vertex, len(start)) + copy(frontier, start) + for len(frontier) > 0 { + // Pop the current vertex + n := len(frontier) + current := frontier[n-1] + frontier = frontier[:n-1] + + // Check if we've seen this already and return... + if _, ok := seen[current]; ok { + continue + } + seen[current] = struct{}{} + + // Visit the current node + if err := cb(current); err != nil { + return err + } + + // Visit targets of this in reverse order. + targets := g.UpEdges(current).List() + for i := len(targets) - 1; i >= 0; i-- { + frontier = append(frontier, targets[i].(Vertex)) + } + } + + return nil +} diff --git a/dag/dag_test.go b/dag/dag_test.go index feead7968..e7b2db8d2 100644 --- a/dag/dag_test.go +++ b/dag/dag_test.go @@ -126,6 +126,68 @@ func TestAcyclicGraphValidate_cycleSelf(t *testing.T) { } } +func TestAcyclicGraphAncestors(t *testing.T) { + var g AcyclicGraph + g.Add(1) + g.Add(2) + g.Add(3) + g.Add(4) + g.Add(5) + g.Connect(BasicEdge(0, 1)) + g.Connect(BasicEdge(1, 2)) + g.Connect(BasicEdge(2, 3)) + g.Connect(BasicEdge(3, 4)) + g.Connect(BasicEdge(4, 5)) + + actual, err := g.Ancestors(2) + if err != nil { + t.Fatalf("err: %#v", err) + } + + expected := []Vertex{3, 4, 5} + + if actual.Len() != len(expected) { + t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected)) + } + + for _, e := range expected { + if !actual.Include(e) { + t.Fatalf("expected: %#v to include: %#v", expected, actual) + } + } +} + +func TestAcyclicGraphDescendents(t *testing.T) { + var g AcyclicGraph + g.Add(1) + g.Add(2) + g.Add(3) + g.Add(4) + g.Add(5) + g.Connect(BasicEdge(0, 1)) + g.Connect(BasicEdge(1, 2)) + g.Connect(BasicEdge(2, 3)) + g.Connect(BasicEdge(3, 4)) + g.Connect(BasicEdge(4, 5)) + + actual, err := g.Descendents(2) + if err != nil { + t.Fatalf("err: %#v", err) + } + + expected := []Vertex{0, 1} + + if actual.Len() != len(expected) { + t.Fatalf("bad length! expected %#v to have len %d", actual, len(expected)) + } + + for _, e := range expected { + if !actual.Include(e) { + t.Fatalf("expected: %#v to include: %#v", expected, actual) + } + } +} + func TestAcyclicGraphWalk(t *testing.T) { var g AcyclicGraph g.Add(1) diff --git a/helper/resource/testing.go b/helper/resource/testing.go index cedadfc72..90cfc175f 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -190,6 +190,7 @@ func testStep( // Build the context opts.Module = mod opts.State = state + opts.Destroy = step.Destroy ctx := terraform.NewContext(&opts) if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { estrs := make([]string, len(es)) @@ -209,7 +210,7 @@ func testStep( } // Plan! - if p, err := ctx.Plan(&terraform.PlanOpts{Destroy: step.Destroy}); err != nil { + if p, err := ctx.Plan(); err != nil { return state, fmt.Errorf( "Error planning: %s", err) } else { diff --git a/terraform/context.go b/terraform/context.go index 86a804548..6beaab636 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -33,6 +33,7 @@ const ( // ContextOpts are the user-configurable options to create a context with // NewContext. type ContextOpts struct { + Destroy bool Diff *Diff Hooks []Hook Module *module.Tree @@ -40,6 +41,7 @@ type ContextOpts struct { State *State Providers map[string]ResourceProviderFactory Provisioners map[string]ResourceProvisionerFactory + Targets []string Variables map[string]string UIInput UIInput @@ -49,6 +51,7 @@ type ContextOpts struct { // perform operations on infrastructure. This structure is built using // NewContext. See the documentation for that. type Context struct { + destroy bool diff *Diff diffLock sync.RWMutex hooks []Hook @@ -58,6 +61,7 @@ type Context struct { sh *stopHook state *State stateLock sync.RWMutex + targets []string uiInput UIInput variables map[string]string @@ -95,12 +99,14 @@ func NewContext(opts *ContextOpts) *Context { } return &Context{ + destroy: opts.Destroy, diff: opts.Diff, hooks: hooks, module: opts.Module, providers: opts.Providers, provisioners: opts.Provisioners, state: state, + targets: opts.Targets, uiInput: opts.UIInput, variables: opts.Variables, @@ -135,6 +141,8 @@ func (c *Context) GraphBuilder() GraphBuilder { Providers: providers, Provisioners: provisioners, State: c.state, + Targets: c.targets, + Destroy: c.destroy, } } @@ -253,7 +261,7 @@ func (c *Context) Apply() (*State, error) { // // Plan also updates the diff of this context to be the diff generated // by the plan, so Apply can be called after. -func (c *Context) Plan(opts *PlanOpts) (*Plan, error) { +func (c *Context) Plan() (*Plan, error) { v := c.acquireRun() defer c.releaseRun(v) @@ -264,7 +272,7 @@ func (c *Context) Plan(opts *PlanOpts) (*Plan, error) { } var operation walkOperation - if opts != nil && opts.Destroy { + if c.destroy { operation = walkPlanDestroy } else { // Set our state to be something temporary. We do this so that diff --git a/terraform/context_test.go b/terraform/context_test.go index abffbf5c3..238f04d04 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -24,7 +24,7 @@ func TestContext2Plan(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -57,7 +57,7 @@ func TestContext2Plan_emptyDiff(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -80,7 +80,7 @@ func TestContext2Plan_minimal(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -103,7 +103,7 @@ func TestContext2Plan_modules(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -126,7 +126,7 @@ func TestContext2Plan_moduleInput(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -149,7 +149,7 @@ func TestContext2Plan_moduleInputComputed(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -175,7 +175,7 @@ func TestContext2Plan_moduleInputFromVar(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -198,7 +198,7 @@ func TestContext2Plan_moduleMultiVar(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -237,7 +237,7 @@ func TestContext2Plan_moduleOrphans(t *testing.T) { State: s, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -282,7 +282,7 @@ func TestContext2Plan_moduleProviderInherit(t *testing.T) { }, }) - _, err := ctx.Plan(nil) + _, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -332,7 +332,7 @@ func TestContext2Plan_moduleProviderDefaults(t *testing.T) { }, }) - _, err := ctx.Plan(nil) + _, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -385,7 +385,7 @@ func TestContext2Plan_moduleProviderDefaultsVar(t *testing.T) { }, }) - _, err := ctx.Plan(nil) + _, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -410,7 +410,7 @@ func TestContext2Plan_moduleVar(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -433,7 +433,7 @@ func TestContext2Plan_moduleVarComputed(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -471,7 +471,7 @@ func TestContext2Plan_nil(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -491,7 +491,7 @@ func TestContext2Plan_computed(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -514,7 +514,7 @@ func TestContext2Plan_computedList(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -537,7 +537,7 @@ func TestContext2Plan_count(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -564,7 +564,7 @@ func TestContext2Plan_countComputed(t *testing.T) { }, }) - _, err := ctx.Plan(nil) + _, err := ctx.Plan() if err == nil { t.Fatal("should error") } @@ -581,7 +581,7 @@ func TestContext2Plan_countIndex(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -604,7 +604,7 @@ func TestContext2Plan_countIndexZero(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -630,7 +630,7 @@ func TestContext2Plan_countVar(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -653,7 +653,7 @@ func TestContext2Plan_countZero(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -676,7 +676,7 @@ func TestContext2Plan_countOneIndex(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -731,7 +731,7 @@ func TestContext2Plan_countDecreaseToOne(t *testing.T) { State: s, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -774,7 +774,7 @@ func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) { State: s, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -817,7 +817,7 @@ func TestContext2Plan_countIncreaseFromOne(t *testing.T) { State: s, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -875,7 +875,7 @@ func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { State: s, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -917,10 +917,11 @@ func TestContext2Plan_destroy(t *testing.T) { Providers: map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), }, - State: s, + State: s, + Destroy: true, }) - plan, err := ctx.Plan(&PlanOpts{Destroy: true}) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -971,10 +972,11 @@ func TestContext2Plan_moduleDestroy(t *testing.T) { Providers: map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), }, - State: s, + State: s, + Destroy: true, }) - plan, err := ctx.Plan(&PlanOpts{Destroy: true}) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -1020,10 +1022,11 @@ func TestContext2Plan_moduleDestroyMultivar(t *testing.T) { Providers: map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), }, - State: s, + State: s, + Destroy: true, }) - plan, err := ctx.Plan(&PlanOpts{Destroy: true}) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -1051,7 +1054,7 @@ func TestContext2Plan_pathVar(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -1118,7 +1121,7 @@ func TestContext2Plan_diffVar(t *testing.T) { }, nil } - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -1143,7 +1146,7 @@ func TestContext2Plan_hook(t *testing.T) { }, }) - _, err := ctx.Plan(nil) + _, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -1183,7 +1186,7 @@ func TestContext2Plan_orphan(t *testing.T) { State: s, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -1221,7 +1224,7 @@ func TestContext2Plan_state(t *testing.T) { State: s, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -1273,7 +1276,7 @@ func TestContext2Plan_taint(t *testing.T) { State: s, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -1324,7 +1327,7 @@ func TestContext2Plan_multiple_taint(t *testing.T) { State: s, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -1357,7 +1360,7 @@ func TestContext2Plan_provider(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -1377,7 +1380,7 @@ func TestContext2Plan_varMultiCountOne(t *testing.T) { }, }) - plan, err := ctx.Plan(nil) + plan, err := ctx.Plan() if err != nil { t.Fatalf("err: %s", err) } @@ -1399,7 +1402,7 @@ func TestContext2Plan_varListErr(t *testing.T) { }, }) - _, err := ctx.Plan(nil) + _, err := ctx.Plan() if err == nil { t.Fatal("should error") } @@ -2468,7 +2471,7 @@ func TestContext2Input(t *testing.T) { t.Fatalf("err: %s", err) } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -2513,7 +2516,7 @@ func TestContext2Input_provider(t *testing.T) { t.Fatalf("err: %s", err) } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -2590,7 +2593,7 @@ func TestContext2Input_providerId(t *testing.T) { t.Fatalf("err: %s", err) } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -2638,7 +2641,7 @@ func TestContext2Input_providerOnly(t *testing.T) { t.Fatalf("err: %s", err) } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -2693,7 +2696,7 @@ func TestContext2Input_providerVars(t *testing.T) { t.Fatalf("err: %s", err) } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -2741,7 +2744,7 @@ func TestContext2Input_varOnly(t *testing.T) { t.Fatalf("err: %s", err) } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -2787,7 +2790,7 @@ func TestContext2Input_varOnlyUnset(t *testing.T) { t.Fatalf("err: %s", err) } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -2815,7 +2818,7 @@ func TestContext2Apply(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -2848,7 +2851,7 @@ func TestContext2Apply_emptyModule(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -2896,7 +2899,7 @@ func TestContext2Apply_createBeforeDestroy(t *testing.T) { State: state, }) - if p, err := ctx.Plan(nil); err != nil { + if p, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } else { t.Logf(p.String()) @@ -2950,7 +2953,7 @@ func TestContext2Apply_createBeforeDestroyUpdate(t *testing.T) { State: state, }) - if p, err := ctx.Plan(nil); err != nil { + if p, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } else { t.Logf(p.String()) @@ -2985,7 +2988,7 @@ func TestContext2Apply_minimal(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3013,7 +3016,7 @@ func TestContext2Apply_badDiff(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3071,7 +3074,7 @@ func TestContext2Apply_cancel(t *testing.T) { }, nil } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3112,7 +3115,7 @@ func TestContext2Apply_compute(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3181,7 +3184,7 @@ func TestContext2Apply_countDecrease(t *testing.T) { State: s, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3241,7 +3244,7 @@ func TestContext2Apply_countDecreaseToOne(t *testing.T) { State: s, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3303,7 +3306,7 @@ func TestContext2Apply_countDecreaseToOneCorrupted(t *testing.T) { State: s, }) - if p, err := ctx.Plan(nil); err != nil { + if p, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } else { testStringMatch(t, p, testTerraformApplyCountDecToOneCorruptedPlanStr) @@ -3354,7 +3357,7 @@ func TestContext2Apply_countTainted(t *testing.T) { State: s, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3382,7 +3385,7 @@ func TestContext2Apply_countVariable(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3410,7 +3413,7 @@ func TestContext2Apply_module(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3439,9 +3442,10 @@ func TestContext2Apply_moduleVarResourceCount(t *testing.T) { Variables: map[string]string{ "count": "2", }, + Destroy: true, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3459,7 +3463,7 @@ func TestContext2Apply_moduleVarResourceCount(t *testing.T) { }, }) - if _, err := ctx.Plan(&PlanOpts{Destroy: true}); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3481,7 +3485,7 @@ func TestContext2Apply_moduleBool(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3515,7 +3519,7 @@ func TestContext2Apply_multiProvider(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3548,7 +3552,7 @@ func TestContext2Apply_nilDiff(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3588,7 +3592,7 @@ func TestContext2Apply_Provisioner_compute(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3633,7 +3637,7 @@ func TestContext2Apply_provisionerCreateFail(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3672,7 +3676,7 @@ func TestContext2Apply_provisionerCreateFailNoId(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3712,7 +3716,7 @@ func TestContext2Apply_provisionerFail(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3767,7 +3771,7 @@ func TestContext2Apply_provisionerFail_createBeforeDestroy(t *testing.T) { State: state, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3816,7 +3820,7 @@ func TestContext2Apply_error_createBeforeDestroy(t *testing.T) { } p.DiffFn = testDiffFn - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3874,7 +3878,7 @@ func TestContext2Apply_errorDestroy_createBeforeDestroy(t *testing.T) { } p.DiffFn = testDiffFn - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3931,7 +3935,7 @@ func TestContext2Apply_multiDepose_createBeforeDestroy(t *testing.T) { } } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3955,7 +3959,7 @@ aws_instance.web: (1 deposed) State: state, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -3983,7 +3987,7 @@ aws_instance.web: (2 deposed) } createdInstanceId = "qux" - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } state, err = ctx.Apply() @@ -4005,7 +4009,7 @@ aws_instance.web: (1 deposed) } createdInstanceId = "quux" - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } state, err = ctx.Apply() @@ -4045,7 +4049,7 @@ func TestContext2Apply_provisionerResourceRef(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4091,7 +4095,7 @@ func TestContext2Apply_provisionerSelfRef(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4144,7 +4148,7 @@ func TestContext2Apply_provisionerMultiSelfRef(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4192,7 +4196,7 @@ func TestContext2Apply_Provisioner_Diff(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4229,7 +4233,7 @@ func TestContext2Apply_Provisioner_Diff(t *testing.T) { State: state, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4300,7 +4304,7 @@ func TestContext2Apply_outputDiffVars(t *testing.T) { }, nil } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } if _, err := ctx.Apply(); err != nil { @@ -4363,7 +4367,7 @@ func TestContext2Apply_Provisioner_ConnInfo(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4399,22 +4403,32 @@ func TestContext2Apply_destroy(t *testing.T) { }) // First plan and apply a create operation - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } - if _, err := ctx.Apply(); err != nil { + state, err := ctx.Apply() + if err != nil { t.Fatalf("err: %s", err) } // Next, plan and apply a destroy operation - if _, err := ctx.Plan(&PlanOpts{Destroy: true}); err != nil { + h.Active = true + ctx = testContext2(t, &ContextOpts{ + Destroy: true, + State: state, + Module: m, + Hooks: []Hook{h}, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } - h.Active = true - - state, err := ctx.Apply() + state, err = ctx.Apply() if err != nil { t.Fatalf("err: %s", err) } @@ -4430,7 +4444,7 @@ func TestContext2Apply_destroy(t *testing.T) { expected2 := []string{"aws_instance.bar", "aws_instance.foo"} actual2 := h.IDs if !reflect.DeepEqual(actual2, expected2) { - t.Fatalf("bad: %#v", actual2) + t.Fatalf("expected: %#v\n\ngot:%#v", expected2, actual2) } } @@ -4449,22 +4463,33 @@ func TestContext2Apply_destroyOutputs(t *testing.T) { }) // First plan and apply a create operation - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } - if _, err := ctx.Apply(); err != nil { + state, err := ctx.Apply() + + if err != nil { t.Fatalf("err: %s", err) } // Next, plan and apply a destroy operation - if _, err := ctx.Plan(&PlanOpts{Destroy: true}); err != nil { + h.Active = true + ctx = testContext2(t, &ContextOpts{ + Destroy: true, + State: state, + Module: m, + Hooks: []Hook{h}, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } - h.Active = true - - state, err := ctx.Apply() + state, err = ctx.Apply() if err != nil { t.Fatalf("err: %s", err) } @@ -4520,7 +4545,7 @@ func TestContext2Apply_destroyOrphan(t *testing.T) { }, nil } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4577,10 +4602,11 @@ func TestContext2Apply_destroyTaintedProvisioner(t *testing.T) { Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, - State: s, + State: s, + Destroy: true, }) - if _, err := ctx.Plan(&PlanOpts{Destroy: true}); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4638,7 +4664,7 @@ func TestContext2Apply_error(t *testing.T) { }, nil } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4705,7 +4731,7 @@ func TestContext2Apply_errorPartial(t *testing.T) { }, nil } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4740,7 +4766,7 @@ func TestContext2Apply_hook(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4788,7 +4814,7 @@ func TestContext2Apply_idAttr(t *testing.T) { }, nil } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4822,7 +4848,7 @@ func TestContext2Apply_output(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4850,7 +4876,7 @@ func TestContext2Apply_outputInvalid(t *testing.T) { }, }) - _, err := ctx.Plan(nil) + _, err := ctx.Plan() if err == nil { t.Fatalf("err: %s", err) } @@ -4871,7 +4897,7 @@ func TestContext2Apply_outputList(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4899,7 +4925,7 @@ func TestContext2Apply_outputMulti(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4927,7 +4953,7 @@ func TestContext2Apply_outputMultiIndex(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -4992,7 +5018,7 @@ func TestContext2Apply_taint(t *testing.T) { State: s, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -5057,7 +5083,7 @@ func TestContext2Apply_taintDep(t *testing.T) { State: s, }) - if p, err := ctx.Plan(nil); err != nil { + if p, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } else { t.Logf("plan: %s", p) @@ -5120,7 +5146,7 @@ func TestContext2Apply_taintDepRequiresNew(t *testing.T) { State: s, }) - if p, err := ctx.Plan(nil); err != nil { + if p, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } else { t.Logf("plan: %s", p) @@ -5150,7 +5176,7 @@ func TestContext2Apply_unknownAttribute(t *testing.T) { }, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -5190,7 +5216,7 @@ func TestContext2Apply_vars(t *testing.T) { t.Fatalf("bad: %s", e) } - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -5248,7 +5274,7 @@ func TestContext2Apply_createBefore_depends(t *testing.T) { State: state, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } @@ -5357,7 +5383,7 @@ func TestContext2Apply_singleDestroy(t *testing.T) { State: state, }) - if _, err := ctx.Plan(nil); err != nil { + if _, err := ctx.Plan(); err != nil { t.Fatalf("err: %s", err) } diff --git a/terraform/graph_builder.go b/terraform/graph_builder.go index 4d5726954..d42f6bfe0 100644 --- a/terraform/graph_builder.go +++ b/terraform/graph_builder.go @@ -65,6 +65,13 @@ type BuiltinGraphBuilder struct { // Provisioners is the list of provisioners supported. Provisioners []string + + // Targets is the user-specified list of resources to target. + Targets []string + + // Destroy is set to true when we're in a `terraform destroy` or a + // `terraform plan -destroy` + Destroy bool } // Build builds the graph according to the steps returned by Steps. @@ -104,6 +111,10 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer { }, }, + // Optionally reduces the graph to a user-specified list of targets and + // their dependencies. + &TargetsTransformer{Targets: b.Targets, Destroy: b.Destroy}, + // Create the destruction nodes &DestroyTransformer{}, &CreateBeforeDestroyTransformer{}, diff --git a/terraform/plan.go b/terraform/plan.go index e73fde383..715136edc 100644 --- a/terraform/plan.go +++ b/terraform/plan.go @@ -18,15 +18,6 @@ func init() { gob.Register(make(map[string]string)) } -// PlanOpts are the options used to generate an execution plan for -// Terraform. -type PlanOpts struct { - // If set to true, then the generated plan will destroy all resources - // that are created. Otherwise, it will move towards the desired state - // specified in the configuration. - Destroy bool -} - // Plan represents a single Terraform execution plan, which contains // all the information necessary to make an infrastructure change. type Plan struct { diff --git a/terraform/test-fixtures/transform-targets-basic/main.tf b/terraform/test-fixtures/transform-targets-basic/main.tf new file mode 100644 index 000000000..b845a1de6 --- /dev/null +++ b/terraform/test-fixtures/transform-targets-basic/main.tf @@ -0,0 +1,16 @@ +resource "aws_vpc" "me" {} + +resource "aws_subnet" "me" { + vpc_id = "${aws_vpc.me.id}" +} + +resource "aws_instance" "me" { + subnet_id = "${aws_subnet.me.id}" +} + +resource "aws_vpc" "notme" {} +resource "aws_subnet" "notme" {} +resource "aws_instance" "notme" {} +resource "aws_instance" "notmeeither" { + name = "${aws_instance.me.id}" +} diff --git a/terraform/test-fixtures/transform-targets-destroy/main.tf b/terraform/test-fixtures/transform-targets-destroy/main.tf new file mode 100644 index 000000000..da99de43c --- /dev/null +++ b/terraform/test-fixtures/transform-targets-destroy/main.tf @@ -0,0 +1,18 @@ +resource "aws_vpc" "notme" {} + +resource "aws_subnet" "notme" { + vpc_id = "${aws_vpc.notme.id}" +} + +resource "aws_instance" "me" { + subnet_id = "${aws_subnet.notme.id}" +} + +resource "aws_instance" "notme" {} +resource "aws_instance" "metoo" { + name = "${aws_instance.me.id}" +} + +resource "aws_elb" "me" { + instances = "${aws_instance.me.*.id}" +} diff --git a/terraform/transform_targets.go b/terraform/transform_targets.go new file mode 100644 index 000000000..0897ef7f9 --- /dev/null +++ b/terraform/transform_targets.go @@ -0,0 +1,77 @@ +package terraform + +import "github.com/hashicorp/terraform/dag" + +// TargetsTransformer is a GraphTransformer that, when the user specifies a +// list of resources to target, limits the graph to only those resources and +// their dependencies. +type TargetsTransformer struct { + // List of targeted resource names specified by the user + Targets []string + + // Set to true when we're in a `terraform destroy` or a + // `terraform plan -destroy` + Destroy bool +} + +func (t *TargetsTransformer) Transform(g *Graph) error { + if len(t.Targets) > 0 { + targetedNodes, err := t.selectTargetedNodes(g) + if err != nil { + return err + } + + for _, v := range g.Vertices() { + if !targetedNodes.Include(v) { + g.Remove(v) + } + } + } + return nil +} + +func (t *TargetsTransformer) selectTargetedNodes(g *Graph) (*dag.Set, error) { + targetedNodes := new(dag.Set) + for _, v := range g.Vertices() { + // Keep all providers; they'll be pruned later if necessary + if r, ok := v.(GraphNodeProvider); ok { + targetedNodes.Add(r) + continue + } + + // For the remaining filter, we only care about Resources and their deps + r, ok := v.(*GraphNodeConfigResource) + if !ok { + continue + } + + if t.resourceIsTarget(r) { + targetedNodes.Add(r) + + var deps *dag.Set + var err error + if t.Destroy { + deps, err = g.Descendents(r) + } else { + deps, err = g.Ancestors(r) + } + if err != nil { + return nil, err + } + + for _, d := range deps.List() { + targetedNodes.Add(d) + } + } + } + return targetedNodes, nil +} + +func (t *TargetsTransformer) resourceIsTarget(r *GraphNodeConfigResource) bool { + for _, target := range t.Targets { + if target == r.Name() { + return true + } + } + return false +} diff --git a/terraform/transform_targets_test.go b/terraform/transform_targets_test.go new file mode 100644 index 000000000..2daa72827 --- /dev/null +++ b/terraform/transform_targets_test.go @@ -0,0 +1,71 @@ +package terraform + +import ( + "strings" + "testing" +) + +func TestTargetsTransformer(t *testing.T) { + mod := testModule(t, "transform-targets-basic") + + g := Graph{Path: RootModulePath} + { + tf := &ConfigTransformer{Module: mod} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &TargetsTransformer{Targets: []string{"aws_instance.me"}} + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(` +aws_instance.me + aws_subnet.me +aws_subnet.me + aws_vpc.me +aws_vpc.me + `) + if actual != expected { + t.Fatalf("bad:\n\nexpected:\n%s\n\ngot:\n%s\n", expected, actual) + } +} + +func TestTargetsTransformer_destroy(t *testing.T) { + mod := testModule(t, "transform-targets-destroy") + + g := Graph{Path: RootModulePath} + { + tf := &ConfigTransformer{Module: mod} + if err := tf.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + { + transform := &TargetsTransformer{ + Targets: []string{"aws_instance.me"}, + Destroy: true, + } + if err := transform.Transform(&g); err != nil { + t.Fatalf("err: %s", err) + } + } + + actual := strings.TrimSpace(g.String()) + expected := strings.TrimSpace(` +aws_elb.me + aws_instance.me +aws_instance.me +aws_instance.metoo + aws_instance.me + `) + if actual != expected { + t.Fatalf("bad:\n\nexpected:\n%s\n\ngot:\n%s\n", expected, actual) + } +} From 23d005b0e8146be3e31731ff7f27a02d5148d90d Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Fri, 27 Mar 2015 17:24:15 -0500 Subject: [PATCH 275/295] core: docs for targeted operations --- website/source/docs/commands/apply.html.markdown | 3 +++ website/source/docs/commands/destroy.html.markdown | 6 ++++++ website/source/docs/commands/plan.html.markdown | 3 +++ website/source/docs/commands/refresh.html.markdown | 3 +++ 4 files changed, 15 insertions(+) diff --git a/website/source/docs/commands/apply.html.markdown b/website/source/docs/commands/apply.html.markdown index c7e2c27da..6414f0741 100644 --- a/website/source/docs/commands/apply.html.markdown +++ b/website/source/docs/commands/apply.html.markdown @@ -44,6 +44,9 @@ The command-line flags are all optional. The list of available flags are: * `-state-out=path` - Path to write updated state file. By default, the `-state` path will be used. +* `-target=resource` - Resource to target. Operation will be limited to this + resource and its dependencies. This flag can be used multiple times. + * `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag can be set multiple times. diff --git a/website/source/docs/commands/destroy.html.markdown b/website/source/docs/commands/destroy.html.markdown index 4ea84f880..0a0f3a738 100644 --- a/website/source/docs/commands/destroy.html.markdown +++ b/website/source/docs/commands/destroy.html.markdown @@ -21,3 +21,9 @@ confirmation before destroying. This command accepts all the flags that the [apply command](/docs/commands/apply.html) accepts. If `-force` is set, then the destroy confirmation will not be shown. + +The `-target` flag, instead of affecting "dependencies" will instead also +destroy any resources that _depend on_ the target(s) specified. + +The behavior of any `terraform destroy` command can be previewed at any time +with an equivalent `terraform plan -destroy` command. diff --git a/website/source/docs/commands/plan.html.markdown b/website/source/docs/commands/plan.html.markdown index 14c10c5da..fe5e90090 100644 --- a/website/source/docs/commands/plan.html.markdown +++ b/website/source/docs/commands/plan.html.markdown @@ -45,6 +45,9 @@ The command-line flags are all optional. The list of available flags are: * `-state=path` - Path to the state file. Defaults to "terraform.tfstate". +* `-target=resource` - Resource to target. Operation will be limited to this + resource and its dependencies. This flag can be used multiple times. + * `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag can be set multiple times. diff --git a/website/source/docs/commands/refresh.html.markdown b/website/source/docs/commands/refresh.html.markdown index cc797ca38..6d15e1a40 100644 --- a/website/source/docs/commands/refresh.html.markdown +++ b/website/source/docs/commands/refresh.html.markdown @@ -36,6 +36,9 @@ The command-line flags are all optional. The list of available flags are: * `-state-out=path` - Path to write updated state file. By default, the `-state` path will be used. +* `-target=resource` - Resource to target. Operation will be limited to this + resource and its dependencies. This flag can be used multiple times. + * `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag can be set multiple times. From 40ebfb5cccd27fa38d0f693e2d0b18ef58a0212e Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Mon, 30 Mar 2015 15:17:28 -0500 Subject: [PATCH 276/295] core: fill out context tests for targeted ops --- terraform/context_test.go | 164 ++++++++++++++++++ .../test-fixtures/apply-targeted/main.tf | 7 + terraform/test-fixtures/plan-targeted/main.tf | 7 + .../test-fixtures/refresh-targeted/main.tf | 8 + 4 files changed, 186 insertions(+) create mode 100644 terraform/test-fixtures/apply-targeted/main.tf create mode 100644 terraform/test-fixtures/plan-targeted/main.tf create mode 100644 terraform/test-fixtures/refresh-targeted/main.tf diff --git a/terraform/context_test.go b/terraform/context_test.go index 238f04d04..67abb101a 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -1339,6 +1339,40 @@ func TestContext2Plan_multiple_taint(t *testing.T) { } } +func TestContext2Plan_targeted(t *testing.T) { + m := testModule(t, "plan-targeted") + p := testProvider("aws") + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Targets: []string{"aws_instance.foo"}, + }) + + plan, err := ctx.Plan() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(` +DIFF: + +CREATE: aws_instance.foo + num: "" => "2" + type: "" => "aws_instance" + +STATE: + + + `) + if actual != expected { + t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, actual) + } +} + func TestContext2Plan_provider(t *testing.T) { m := testModule(t, "plan-provider") p := testProvider("aws") @@ -1460,6 +1494,47 @@ func TestContext2Refresh(t *testing.T) { } } +func TestContext2Refresh_targeted(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-targeted") + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_vpc.metoo": resourceState("aws_vpc", "vpc-abc123"), + "aws_instance.notme": resourceState("aws_instance", "i-bcd345"), + "aws_instance.me": resourceState("aws_instance", "i-abc123"), + "aws_elb.meneither": resourceState("aws_elb", "lb-abc123"), + }, + }, + }, + }, + Targets: []string{"aws_instance.me"}, + }) + + refreshedResources := make([]string, 0, 2) + p.RefreshFn = func(i *InstanceInfo, is *InstanceState) (*InstanceState, error) { + refreshedResources = append(refreshedResources, i.Id) + return is, nil + } + + _, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := []string{"aws_vpc.metoo", "aws_instance.me"} + if !reflect.DeepEqual(refreshedResources, expected) { + t.Fatalf("expected: %#v, got: %#v", expected, refreshedResources) + } +} + func TestContext2Refresh_delete(t *testing.T) { p := testProvider("aws") m := testModule(t, "refresh-basic") @@ -5164,6 +5239,86 @@ func TestContext2Apply_taintDepRequiresNew(t *testing.T) { } } +func TestContext2Apply_targeted(t *testing.T) { + m := testModule(t, "apply-targeted") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Targets: []string{"aws_instance.foo"}, + }) + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + mod := state.RootModule() + if len(mod.Resources) != 1 { + t.Fatalf("expected 1 resource, got: %#v", mod.Resources) + } + + checkStateString(t, state, ` +aws_instance.foo: + ID = foo + num = 2 + type = aws_instance + `) +} + +func TestContext2Apply_targetedDestroy(t *testing.T) { + m := testModule(t, "apply-targeted") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo": resourceState("aws_instance", "i-bcd345"), + "aws_instance.bar": resourceState("aws_instance", "i-abc123"), + }, + }, + }, + }, + Targets: []string{"aws_instance.foo"}, + Destroy: true, + }) + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + mod := state.RootModule() + if len(mod.Resources) != 1 { + t.Fatalf("expected 1 resource, got: %#v", mod.Resources) + } + + checkStateString(t, state, ` +aws_instance.bar: + ID = i-abc123 + `) +} + func TestContext2Apply_unknownAttribute(t *testing.T) { m := testModule(t, "apply-unknown") p := testProvider("aws") @@ -5553,6 +5708,15 @@ func checkStateString(t *testing.T, state *State, expected string) { } } +func resourceState(resourceType, resourceID string) *ResourceState { + return &ResourceState{ + Type: resourceType, + Primary: &InstanceState{ + ID: resourceID, + }, + } +} + const testContextGraph = ` root: root aws_instance.bar diff --git a/terraform/test-fixtures/apply-targeted/main.tf b/terraform/test-fixtures/apply-targeted/main.tf new file mode 100644 index 000000000..b07fc97f4 --- /dev/null +++ b/terraform/test-fixtures/apply-targeted/main.tf @@ -0,0 +1,7 @@ +resource "aws_instance" "foo" { + num = "2" +} + +resource "aws_instance" "bar" { + foo = "bar" +} diff --git a/terraform/test-fixtures/plan-targeted/main.tf b/terraform/test-fixtures/plan-targeted/main.tf new file mode 100644 index 000000000..1b6cdae67 --- /dev/null +++ b/terraform/test-fixtures/plan-targeted/main.tf @@ -0,0 +1,7 @@ +resource "aws_instance" "foo" { + num = "2" +} + +resource "aws_instance" "bar" { + foo = "${aws_instance.foo.num}" +} diff --git a/terraform/test-fixtures/refresh-targeted/main.tf b/terraform/test-fixtures/refresh-targeted/main.tf new file mode 100644 index 000000000..3a7618464 --- /dev/null +++ b/terraform/test-fixtures/refresh-targeted/main.tf @@ -0,0 +1,8 @@ +resource "aws_vpc" "metoo" {} +resource "aws_instance" "notme" { } +resource "aws_instance" "me" { + vpc_id = "${aws_vpc.metoo.id}" +} +resource "aws_elb" "meneither" { + instances = ["${aws_instance.me.*.id}"] +} From c6300d511c51aa33e53e127484332d852f92c4f4 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Mon, 30 Mar 2015 19:02:36 -0500 Subject: [PATCH 277/295] core: formalize resource addressing Only used in targets for now. The plan is to use this for interpolation as well. This allows us to target: * individual resources expanded by `count` using bracket / index notation. * deposed / tainted resources with an `InstanceType` field after name Docs to follow. --- terraform/context_test.go | 205 +++++++++++++++++ terraform/graph_builder.go | 6 +- terraform/graph_config_node.go | 45 +++- terraform/instancetype.go | 13 ++ terraform/instancetype_string.go | 16 ++ terraform/resource_address.go | 98 +++++++++ terraform/resource_address_test.go | 207 ++++++++++++++++++ .../apply-targeted-count/main.tf | 7 + .../refresh-targeted-count/main.tf | 9 + terraform/transform_orphan.go | 13 ++ terraform/transform_resource.go | 48 +++- terraform/transform_targets.go | 44 +++- 12 files changed, 696 insertions(+), 15 deletions(-) create mode 100644 terraform/instancetype.go create mode 100644 terraform/instancetype_string.go create mode 100644 terraform/resource_address.go create mode 100644 terraform/resource_address_test.go create mode 100644 terraform/test-fixtures/apply-targeted-count/main.tf create mode 100644 terraform/test-fixtures/refresh-targeted-count/main.tf diff --git a/terraform/context_test.go b/terraform/context_test.go index 67abb101a..f582ae679 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -1535,6 +1535,98 @@ func TestContext2Refresh_targeted(t *testing.T) { } } +func TestContext2Refresh_targetedCount(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-targeted-count") + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_vpc.metoo": resourceState("aws_vpc", "vpc-abc123"), + "aws_instance.notme": resourceState("aws_instance", "i-bcd345"), + "aws_instance.me.0": resourceState("aws_instance", "i-abc123"), + "aws_instance.me.1": resourceState("aws_instance", "i-cde567"), + "aws_instance.me.2": resourceState("aws_instance", "i-cde789"), + "aws_elb.meneither": resourceState("aws_elb", "lb-abc123"), + }, + }, + }, + }, + Targets: []string{"aws_instance.me"}, + }) + + refreshedResources := make([]string, 0, 2) + p.RefreshFn = func(i *InstanceInfo, is *InstanceState) (*InstanceState, error) { + refreshedResources = append(refreshedResources, i.Id) + return is, nil + } + + _, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Target didn't specify index, so we should get all our instances + expected := []string{ + "aws_vpc.metoo", + "aws_instance.me.0", + "aws_instance.me.1", + "aws_instance.me.2", + } + if !reflect.DeepEqual(refreshedResources, expected) { + t.Fatalf("expected: %#v, got: %#v", expected, refreshedResources) + } +} + +func TestContext2Refresh_targetedCountIndex(t *testing.T) { + p := testProvider("aws") + m := testModule(t, "refresh-targeted-count") + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_vpc.metoo": resourceState("aws_vpc", "vpc-abc123"), + "aws_instance.notme": resourceState("aws_instance", "i-bcd345"), + "aws_instance.me.0": resourceState("aws_instance", "i-abc123"), + "aws_instance.me.1": resourceState("aws_instance", "i-cde567"), + "aws_instance.me.2": resourceState("aws_instance", "i-cde789"), + "aws_elb.meneither": resourceState("aws_elb", "lb-abc123"), + }, + }, + }, + }, + Targets: []string{"aws_instance.me[0]"}, + }) + + refreshedResources := make([]string, 0, 2) + p.RefreshFn = func(i *InstanceInfo, is *InstanceState) (*InstanceState, error) { + refreshedResources = append(refreshedResources, i.Id) + return is, nil + } + + _, err := ctx.Refresh() + if err != nil { + t.Fatalf("err: %s", err) + } + + expected := []string{"aws_vpc.metoo", "aws_instance.me.0"} + if !reflect.DeepEqual(refreshedResources, expected) { + t.Fatalf("expected: %#v, got: %#v", expected, refreshedResources) + } +} + func TestContext2Refresh_delete(t *testing.T) { p := testProvider("aws") m := testModule(t, "refresh-basic") @@ -5274,6 +5366,66 @@ aws_instance.foo: `) } +func TestContext2Apply_targetedCount(t *testing.T) { + m := testModule(t, "apply-targeted-count") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Targets: []string{"aws_instance.foo"}, + }) + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + checkStateString(t, state, ` +aws_instance.foo.0: + ID = foo +aws_instance.foo.1: + ID = foo +aws_instance.foo.2: + ID = foo + `) +} + +func TestContext2Apply_targetedCountIndex(t *testing.T) { + m := testModule(t, "apply-targeted-count") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + Targets: []string{"aws_instance.foo[1]"}, + }) + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + checkStateString(t, state, ` +aws_instance.foo.1: + ID = foo + `) +} + func TestContext2Apply_targetedDestroy(t *testing.T) { m := testModule(t, "apply-targeted") p := testProvider("aws") @@ -5319,6 +5471,59 @@ aws_instance.bar: `) } +func TestContext2Apply_targetedDestroyCountIndex(t *testing.T) { + m := testModule(t, "apply-targeted-count") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo.0": resourceState("aws_instance", "i-bcd345"), + "aws_instance.foo.1": resourceState("aws_instance", "i-bcd345"), + "aws_instance.foo.2": resourceState("aws_instance", "i-bcd345"), + "aws_instance.bar.0": resourceState("aws_instance", "i-abc123"), + "aws_instance.bar.1": resourceState("aws_instance", "i-abc123"), + "aws_instance.bar.2": resourceState("aws_instance", "i-abc123"), + }, + }, + }, + }, + Targets: []string{ + "aws_instance.foo[2]", + "aws_instance.bar[1]", + }, + Destroy: true, + }) + + if _, err := ctx.Plan(); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + checkStateString(t, state, ` +aws_instance.bar.0: + ID = i-abc123 +aws_instance.bar.2: + ID = i-abc123 +aws_instance.foo.0: + ID = i-bcd345 +aws_instance.foo.1: + ID = i-bcd345 + `) +} + func TestContext2Apply_unknownAttribute(t *testing.T) { m := testModule(t, "apply-unknown") p := testProvider("aws") diff --git a/terraform/graph_builder.go b/terraform/graph_builder.go index d42f6bfe0..03c59f958 100644 --- a/terraform/graph_builder.go +++ b/terraform/graph_builder.go @@ -89,7 +89,11 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer { return []GraphTransformer{ // Create all our resources from the configuration and state &ConfigTransformer{Module: b.Root}, - &OrphanTransformer{State: b.State, Module: b.Root}, + &OrphanTransformer{ + State: b.State, + Module: b.Root, + Targeting: (len(b.Targets) > 0), + }, // Provider-related transformations &MissingProviderTransformer{Providers: b.Providers}, diff --git a/terraform/graph_config_node.go b/terraform/graph_config_node.go index 625992f3f..ddb96da2c 100644 --- a/terraform/graph_config_node.go +++ b/terraform/graph_config_node.go @@ -21,6 +21,26 @@ type graphNodeConfig interface { GraphNodeDependent } +// GraphNodeAddressable is an interface that all graph nodes for the +// configuration graph need to implement in order to be be addressed / targeted +// properly. +type GraphNodeAddressable interface { + graphNodeConfig + + ResourceAddress() *ResourceAddress +} + +// GraphNodeTargetable is an interface for graph nodes to implement when they +// need to be told about incoming targets. This is useful for nodes that need +// to respect targets as they dynamically expand. Note that the list of targets +// provided will contain every target provided, and each implementing graph +// node must filter this list to targets considered relevant. +type GraphNodeTargetable interface { + GraphNodeAddressable + + SetTargets([]ResourceAddress) +} + // GraphNodeConfigModule represents a module within the configuration graph. type GraphNodeConfigModule struct { Path []string @@ -191,6 +211,9 @@ type GraphNodeConfigResource struct { // If this is set to anything other than destroyModeNone, then this // resource represents a resource that will be destroyed in some way. DestroyMode GraphNodeDestroyMode + + // Used during DynamicExpand to target indexes + Targets []ResourceAddress } func (n *GraphNodeConfigResource) DependableName() []string { @@ -279,6 +302,7 @@ func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) steps = append(steps, &ResourceCountTransformer{ Resource: n.Resource, Destroy: n.DestroyMode != DestroyNone, + Targets: n.Targets, }) } @@ -289,8 +313,9 @@ func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) // expand orphans, which have all the same semantics in a destroy // as a primary. steps = append(steps, &OrphanTransformer{ - State: state, - View: n.Resource.Id(), + State: state, + View: n.Resource.Id(), + Targeting: (len(n.Targets) > 0), }) steps = append(steps, &DeposedTransformer{ @@ -314,6 +339,22 @@ func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) return b.Build(ctx.Path()) } +// GraphNodeAddressable impl. +func (n *GraphNodeConfigResource) ResourceAddress() *ResourceAddress { + return &ResourceAddress{ + // Indicates no specific index; will match on other three fields + Index: -1, + InstanceType: TypePrimary, + Name: n.Resource.Name, + Type: n.Resource.Type, + } +} + +// GraphNodeTargetable impl. +func (n *GraphNodeConfigResource) SetTargets(targets []ResourceAddress) { + n.Targets = targets +} + // GraphNodeEvalable impl. func (n *GraphNodeConfigResource) EvalTree() EvalNode { return &EvalSequence{ diff --git a/terraform/instancetype.go b/terraform/instancetype.go new file mode 100644 index 000000000..08959717b --- /dev/null +++ b/terraform/instancetype.go @@ -0,0 +1,13 @@ +package terraform + +//go:generate stringer -type=InstanceType instancetype.go + +// InstanceType is an enum of the various types of instances store in the State +type InstanceType int + +const ( + TypeInvalid InstanceType = iota + TypePrimary + TypeTainted + TypeDeposed +) diff --git a/terraform/instancetype_string.go b/terraform/instancetype_string.go new file mode 100644 index 000000000..fc8697644 --- /dev/null +++ b/terraform/instancetype_string.go @@ -0,0 +1,16 @@ +// generated by stringer -type=InstanceType instancetype.go; DO NOT EDIT + +package terraform + +import "fmt" + +const _InstanceType_name = "TypeInvalidTypePrimaryTypeTaintedTypeDeposed" + +var _InstanceType_index = [...]uint8{0, 11, 22, 33, 44} + +func (i InstanceType) String() string { + if i < 0 || i+1 >= InstanceType(len(_InstanceType_index)) { + return fmt.Sprintf("InstanceType(%d)", i) + } + return _InstanceType_name[_InstanceType_index[i]:_InstanceType_index[i+1]] +} diff --git a/terraform/resource_address.go b/terraform/resource_address.go new file mode 100644 index 000000000..b54a923d8 --- /dev/null +++ b/terraform/resource_address.go @@ -0,0 +1,98 @@ +package terraform + +import ( + "fmt" + "regexp" + "strconv" +) + +// ResourceAddress is a way of identifying an individual resource (or, +// eventually, a subset of resources) within the state. It is used for Targets. +type ResourceAddress struct { + Index int + InstanceType InstanceType + Name string + Type string +} + +func ParseResourceAddress(s string) (*ResourceAddress, error) { + matches, err := tokenizeResourceAddress(s) + if err != nil { + return nil, err + } + resourceIndex := -1 + if matches["index"] != "" { + var err error + if resourceIndex, err = strconv.Atoi(matches["index"]); err != nil { + return nil, err + } + } + instanceType := TypePrimary + if matches["instance_type"] != "" { + var err error + if instanceType, err = ParseInstanceType(matches["instance_type"]); err != nil { + return nil, err + } + } + + return &ResourceAddress{ + Index: resourceIndex, + InstanceType: instanceType, + Name: matches["name"], + Type: matches["type"], + }, nil +} + +func (addr *ResourceAddress) Equals(raw interface{}) bool { + other, ok := raw.(*ResourceAddress) + if !ok { + return false + } + + indexMatch := (addr.Index == -1 || + other.Index == -1 || + addr.Index == other.Index) + + return (indexMatch && + addr.InstanceType == other.InstanceType && + addr.Name == other.Name && + addr.Type == other.Type) +} + +func ParseInstanceType(s string) (InstanceType, error) { + switch s { + case "primary": + return TypePrimary, nil + case "deposed": + return TypeDeposed, nil + case "tainted": + return TypeTainted, nil + default: + return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s) + } +} + +func tokenizeResourceAddress(s string) (map[string]string, error) { + // Example of portions of the regexp below using the + // string "aws_instance.web.tainted[1]" + re := regexp.MustCompile(`\A` + + // "aws_instance" + `(?P\w+)\.` + + // "web" + `(?P\w+)` + + // "tainted" (optional, omission implies: "primary") + `(?:\.(?P\w+))?` + + // "1" (optional, omission implies: "0") + `(?:\[(?P\d+)\])?` + + `\z`) + groupNames := re.SubexpNames() + rawMatches := re.FindAllStringSubmatch(s, -1) + if len(rawMatches) != 1 { + return nil, fmt.Errorf("Problem parsing address: %q", s) + } + matches := make(map[string]string) + for i, m := range rawMatches[0] { + matches[groupNames[i]] = m + } + return matches, nil +} diff --git a/terraform/resource_address_test.go b/terraform/resource_address_test.go new file mode 100644 index 000000000..2a8caa1f8 --- /dev/null +++ b/terraform/resource_address_test.go @@ -0,0 +1,207 @@ +package terraform + +import ( + "reflect" + "testing" +) + +func TestParseResourceAddress(t *testing.T) { + cases := map[string]struct { + Input string + Expected *ResourceAddress + }{ + "implicit primary, no specific index": { + Input: "aws_instance.foo", + Expected: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + }, + "implicit primary, explicit index": { + Input: "aws_instance.foo[2]", + Expected: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 2, + }, + }, + "explicit primary, explicit index": { + Input: "aws_instance.foo.primary[2]", + Expected: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 2, + }, + }, + "tainted": { + Input: "aws_instance.foo.tainted", + Expected: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypeTainted, + Index: -1, + }, + }, + "deposed": { + Input: "aws_instance.foo.deposed", + Expected: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypeDeposed, + Index: -1, + }, + }, + } + + for tn, tc := range cases { + out, err := ParseResourceAddress(tc.Input) + if err != nil { + t.Fatalf("unexpected err: %#v", err) + } + + if !reflect.DeepEqual(out, tc.Expected) { + t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out) + } + } +} + +func TestResourceAddressEquals(t *testing.T) { + cases := map[string]struct { + Address *ResourceAddress + Other interface{} + Expect bool + }{ + "basic match": { + Address: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Other: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: true, + }, + "address does not set index": { + Address: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + Other: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 3, + }, + Expect: true, + }, + "other does not set index": { + Address: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 3, + }, + Other: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + Expect: true, + }, + "neither sets index": { + Address: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + Other: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: -1, + }, + Expect: true, + }, + "different type": { + Address: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Other: &ResourceAddress{ + Type: "aws_vpc", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: false, + }, + "different name": { + Address: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Other: &ResourceAddress{ + Type: "aws_instance", + Name: "bar", + InstanceType: TypePrimary, + Index: 0, + }, + Expect: false, + }, + "different instance type": { + Address: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Other: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypeTainted, + Index: 0, + }, + Expect: false, + }, + "different index": { + Address: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 0, + }, + Other: &ResourceAddress{ + Type: "aws_instance", + Name: "foo", + InstanceType: TypePrimary, + Index: 1, + }, + Expect: false, + }, + } + + for tn, tc := range cases { + actual := tc.Address.Equals(tc.Other) + if actual != tc.Expect { + t.Fatalf("%q: expected equals: %t, got %t for:\n%#v\n%#v", + tn, tc.Expect, actual, tc.Address, tc.Other) + } + } +} diff --git a/terraform/test-fixtures/apply-targeted-count/main.tf b/terraform/test-fixtures/apply-targeted-count/main.tf new file mode 100644 index 000000000..cd861898f --- /dev/null +++ b/terraform/test-fixtures/apply-targeted-count/main.tf @@ -0,0 +1,7 @@ +resource "aws_instance" "foo" { + count = 3 +} + +resource "aws_instance" "bar" { + count = 3 +} diff --git a/terraform/test-fixtures/refresh-targeted-count/main.tf b/terraform/test-fixtures/refresh-targeted-count/main.tf new file mode 100644 index 000000000..f564b629c --- /dev/null +++ b/terraform/test-fixtures/refresh-targeted-count/main.tf @@ -0,0 +1,9 @@ +resource "aws_vpc" "metoo" {} +resource "aws_instance" "notme" { } +resource "aws_instance" "me" { + vpc_id = "${aws_vpc.metoo.id}" + count = 3 +} +resource "aws_elb" "meneither" { + instances = ["${aws_instance.me.*.id}"] +} diff --git a/terraform/transform_orphan.go b/terraform/transform_orphan.go index e2a9c7dcd..5de64c65c 100644 --- a/terraform/transform_orphan.go +++ b/terraform/transform_orphan.go @@ -2,6 +2,7 @@ package terraform import ( "fmt" + "log" "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" @@ -25,6 +26,11 @@ type OrphanTransformer struct { // using the graph path. Module *module.Tree + // Targets are user-specified resources to target. We need to be aware of + // these so we don't improperly identify orphans when they've just been + // filtered out of the graph via targeting. + Targeting bool + // View, if non-nil will set a view on the module state. View string } @@ -35,6 +41,13 @@ func (t *OrphanTransformer) Transform(g *Graph) error { return nil } + if t.Targeting { + log.Printf("Skipping orphan transformer because we have targets.") + // If we are in a run where we are targeting nodes, we won't process + // orphans for this run. + return nil + } + // Build up all our state representatives resourceRep := make(map[string]struct{}) for _, v := range g.Vertices() { diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index 8c2a00c78..21774e953 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -12,6 +12,7 @@ import ( type ResourceCountTransformer struct { Resource *config.Resource Destroy bool + Targets []ResourceAddress } func (t *ResourceCountTransformer) Transform(g *Graph) error { @@ -27,7 +28,7 @@ func (t *ResourceCountTransformer) Transform(g *Graph) error { } // For each count, build and add the node - nodes := make([]dag.Vertex, count) + nodes := make([]dag.Vertex, 0, count) for i := 0; i < count; i++ { // Set the index. If our count is 1 we special case it so that // we handle the "resource.0" and "resource" boundary properly. @@ -49,9 +50,14 @@ func (t *ResourceCountTransformer) Transform(g *Graph) error { } } + // Skip nodes if targeting excludes them + if !t.nodeIsTargeted(node) { + continue + } + // Add the node now - nodes[i] = node - g.Add(nodes[i]) + nodes = append(nodes, node) + g.Add(node) } // Make the dependency connections @@ -64,6 +70,25 @@ func (t *ResourceCountTransformer) Transform(g *Graph) error { return nil } +func (t *ResourceCountTransformer) nodeIsTargeted(node dag.Vertex) bool { + // no targets specified, everything stays in the graph + if len(t.Targets) == 0 { + return true + } + addressable, ok := node.(GraphNodeAddressable) + if !ok { + return false + } + + addr := addressable.ResourceAddress() + for _, targetAddr := range t.Targets { + if targetAddr.Equals(addr) { + return true + } + } + return false +} + type graphNodeExpandedResource struct { Index int Resource *config.Resource @@ -77,6 +102,23 @@ func (n *graphNodeExpandedResource) Name() string { return fmt.Sprintf("%s #%d", n.Resource.Id(), n.Index) } +// GraphNodeAddressable impl. +func (n *graphNodeExpandedResource) ResourceAddress() *ResourceAddress { + // We want this to report the logical index properly, so we must undo the + // special case from the expand + index := n.Index + if index == -1 { + index = 0 + } + return &ResourceAddress{ + Index: index, + // TODO: kjkjkj + InstanceType: TypePrimary, + Name: n.Resource.Name, + Type: n.Resource.Type, + } +} + // GraphNodeDependable impl. func (n *graphNodeExpandedResource) DependableName() []string { return []string{ diff --git a/terraform/transform_targets.go b/terraform/transform_targets.go index 0897ef7f9..29a6d53c6 100644 --- a/terraform/transform_targets.go +++ b/terraform/transform_targets.go @@ -16,13 +16,20 @@ type TargetsTransformer struct { func (t *TargetsTransformer) Transform(g *Graph) error { if len(t.Targets) > 0 { - targetedNodes, err := t.selectTargetedNodes(g) + // TODO: duplicated in OrphanTransformer; pull up parsing earlier + addrs, err := t.parseTargetAddresses() + if err != nil { + return err + } + + targetedNodes, err := t.selectTargetedNodes(g, addrs) if err != nil { return err } for _, v := range g.Vertices() { - if !targetedNodes.Include(v) { + if targetedNodes.Include(v) { + } else { g.Remove(v) } } @@ -30,7 +37,20 @@ func (t *TargetsTransformer) Transform(g *Graph) error { return nil } -func (t *TargetsTransformer) selectTargetedNodes(g *Graph) (*dag.Set, error) { +func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) { + addrs := make([]ResourceAddress, len(t.Targets)) + for i, target := range t.Targets { + ta, err := ParseResourceAddress(target) + if err != nil { + return nil, err + } + addrs[i] = *ta + } + return addrs, nil +} + +func (t *TargetsTransformer) selectTargetedNodes( + g *Graph, addrs []ResourceAddress) (*dag.Set, error) { targetedNodes := new(dag.Set) for _, v := range g.Vertices() { // Keep all providers; they'll be pruned later if necessary @@ -39,14 +59,18 @@ func (t *TargetsTransformer) selectTargetedNodes(g *Graph) (*dag.Set, error) { continue } - // For the remaining filter, we only care about Resources and their deps - r, ok := v.(*GraphNodeConfigResource) + // For the remaining filter, we only care about addressable nodes + r, ok := v.(GraphNodeAddressable) if !ok { continue } - if t.resourceIsTarget(r) { + if t.nodeIsTarget(r, addrs) { targetedNodes.Add(r) + // If the node would like to know about targets, tell it. + if n, ok := r.(GraphNodeTargetable); ok { + n.SetTargets(addrs) + } var deps *dag.Set var err error @@ -67,9 +91,11 @@ func (t *TargetsTransformer) selectTargetedNodes(g *Graph) (*dag.Set, error) { return targetedNodes, nil } -func (t *TargetsTransformer) resourceIsTarget(r *GraphNodeConfigResource) bool { - for _, target := range t.Targets { - if target == r.Name() { +func (t *TargetsTransformer) nodeIsTarget( + r GraphNodeAddressable, addrs []ResourceAddress) bool { + addr := r.ResourceAddress() + for _, targetAddr := range addrs { + if targetAddr.Equals(addr) { return true } } From 596511078ab8193a648f86ab690cf52ab77fdb93 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 31 Mar 2015 14:48:57 -0700 Subject: [PATCH 278/295] website: note on docker --- website/source/docs/providers/docker/index.html.markdown | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/source/docs/providers/docker/index.html.markdown b/website/source/docs/providers/docker/index.html.markdown index 73807c12e..9a1f4abe1 100644 --- a/website/source/docs/providers/docker/index.html.markdown +++ b/website/source/docs/providers/docker/index.html.markdown @@ -16,6 +16,12 @@ API hosts. Use the navigation to the left to read about the available resources. +
+Note: The Docker provider is new as of Terraform 0.4. +It is ready to be used but many features are still being added. If there +is a Docker feature missing, please report it in the GitHub repo. +
+ ## Example Usage ``` From d00f32a4f242e2ac5ee7cfff50ddce0b231f031f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 31 Mar 2015 14:53:16 -0700 Subject: [PATCH 279/295] update CHANGELOG --- CHANGELOG.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2485613f8..5d921e071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ BACKWARDS INCOMPATIBILITIES: FEATURES: * **New provider: `dme` (DNSMadeEasy)** [GH-855] + * **New provider: `docker` (Docker)** - Manage container lifecycle + using the standard Docker API. [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] @@ -24,8 +26,8 @@ FEATURES: or system killing Terraform. * **Math operations** in interpolations. You can now do things like `${count.index+1}`. [GH-1068] - * **New AWS SDK:** Move to `aws-sdk-go` (hashicorp/aws-sdk-go), - a fork of the offical `awslabs` repo. We forked for stability while + * **New AWS SDK:** Move to `aws-sdk-go` (hashicorp/aws-sdk-go), + a fork of the offical `awslabs` repo. We forked for stability while `awslabs` refactored the library, and will move back to the officially supported version in the next release. @@ -49,9 +51,9 @@ IMPROVEMENTS: automatically done initially. * providers/google: Add `size` option to disk blocks for instances. [GH-1284] * providers/aws: Improve support for tagging resources. - * providers/aws: Add a short syntax for Route 53 Record names, e.g. + * providers/aws: Add a short syntax for Route 53 Record names, e.g. `www` instead of `www.example.com`. - * providers/aws: Improve dependency violation error handling, when deleting + * providers/aws: Improve dependency violation error handling, when deleting Internet Gateways or Auto Scaling groups [GH-1325]. BUG FIXES: @@ -73,15 +75,15 @@ BUG FIXES: * providers/aws: Longer wait times for route53 records (30 mins). [GH-1164] * providers/aws: Fix support for TXT records in Route 53. [GH-1213] * providers/aws: Fix support for wildcard records in Route 53. [GH-1222] - * providers/aws: Fix issue with ignoring the 'self' attribute of a + * providers/aws: Fix issue with ignoring the 'self' attribute of a Security Group rule. [GH-1223] - * providers/aws: Fix issue with `sql_mode` in RDS parameter group always + * providers/aws: Fix issue with `sql_mode` in RDS parameter group always causing an update. [GH-1225] - * providers/aws: Fix dependency violation with subnets and security groups + * providers/aws: Fix dependency violation with subnets and security groups [GH-1252] - * providers/aws: Fix issue with refreshing `db_subnet_groups` causing an error + * providers/aws: Fix issue with refreshing `db_subnet_groups` causing an error instead of updating state [GH-1254] - * providers/aws: Prevent empty string to be used as default + * providers/aws: Prevent empty string to be used as default `health_check_type` [GH-1052] * providers/aws: Add tags on AWS IG creation, not just on update [GH-1176] * providers/digitalocean: Waits until droplet is ready to be destroyed [GH-1057] From dfee25fc47118a7992b1b3ae83ac57b3b6862b34 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Tue, 31 Mar 2015 17:00:44 -0500 Subject: [PATCH 280/295] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d921e071..24336b5ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ FEATURES: * **New provider: `dme` (DNSMadeEasy)** [GH-855] * **New provider: `docker` (Docker)** - Manage container lifecycle using the standard Docker API. [GH-855] + * **New provider: `openstack` (OpenStack)** - Interact with the many resources + provided by OpenStack. [GH-924] * **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] From a90b9db3971a3b27e05bbc2fcf9180bc750c641d Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Fri, 27 Mar 2015 15:47:28 +0100 Subject: [PATCH 281/295] Bugfix on floating IP assignment The `getFirstNetworkID` does not work correctly because the first network is not always the private network of the instance. As long as the `GET /networks` gives a list containing also public networks we don't have any guarantee that the first network is the one we want. Furthermore, with a loop over the network list we are not able to determine which network is the one we want. Instead of retrieving the network ID and then finding the port ID, it's better to basically take the first port ID of the instance. --- .../resource_openstack_compute_instance_v2.go | 44 +++++-------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go index 5a4c16c7e..b5fe36a10 100644 --- a/builtin/providers/openstack/resource_openstack_compute_instance_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_instance_v2.go @@ -20,7 +20,6 @@ import ( "github.com/rackspace/gophercloud/openstack/compute/v2/images" "github.com/rackspace/gophercloud/openstack/compute/v2/servers" "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" - "github.com/rackspace/gophercloud/openstack/networking/v2/networks" "github.com/rackspace/gophercloud/openstack/networking/v2/ports" "github.com/rackspace/gophercloud/pagination" ) @@ -303,7 +302,7 @@ func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) e } err = assignFloatingIP(networkingClient, extractFloatingIPFromIP(allFloatingIPs, floatingIP), server.ID) if err != nil { - fmt.Errorf("Error assigning floating IP to OpenStack compute instance: %s", err) + return fmt.Errorf("Error assigning floating IP to OpenStack compute instance: %s", err) } } @@ -770,44 +769,18 @@ func extractFloatingIPFromIP(ips []floatingips.FloatingIP, IP string) *floatingi } func assignFloatingIP(networkingClient *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, instanceID string) error { - networkID, err := getFirstNetworkID(networkingClient, instanceID) + portID, err := getInstancePortID(networkingClient, instanceID) if err != nil { return err } - portID, err := getInstancePortID(networkingClient, instanceID, networkID) - _, err = floatingips.Update(networkingClient, floatingIP.ID, floatingips.UpdateOpts{ + return floatingips.Update(networkingClient, floatingIP.ID, floatingips.UpdateOpts{ PortID: portID, - }).Extract() - return err + }).Err } -func getFirstNetworkID(networkingClient *gophercloud.ServiceClient, instanceID string) (string, error) { - pager := networks.List(networkingClient, networks.ListOpts{}) - - var networkdID string - err := pager.EachPage(func(page pagination.Page) (bool, error) { - networkList, err := networks.ExtractNetworks(page) - if err != nil { - return false, err - } - - if len(networkList) > 0 { - networkdID = networkList[0].ID - return false, nil - } - return false, fmt.Errorf("No network found for the instance %s", instanceID) - }) - if err != nil { - return "", err - } - return networkdID, nil - -} - -func getInstancePortID(networkingClient *gophercloud.ServiceClient, instanceID, networkID string) (string, error) { +func getInstancePortID(networkingClient *gophercloud.ServiceClient, instanceID string) (string, error) { pager := ports.List(networkingClient, ports.ListOpts{ - DeviceID: instanceID, - NetworkID: networkID, + DeviceID: instanceID, }) var portID string @@ -826,6 +799,11 @@ func getInstancePortID(networkingClient *gophercloud.ServiceClient, instanceID, if err != nil { return "", err } + + if portID == "" { + return "", fmt.Errorf("Cannot find port for instance %s", instanceID) + } + return portID, nil } From ad19adfdc0b0934d44d1079bdbd25ad22275a3aa Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Tue, 31 Mar 2015 15:49:10 -0700 Subject: [PATCH 282/295] providers/heroku: Add region to example ...since it's required --- website/source/docs/providers/heroku/r/app.html.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/website/source/docs/providers/heroku/r/app.html.markdown b/website/source/docs/providers/heroku/r/app.html.markdown index d05bd2fb0..9e51d62f2 100644 --- a/website/source/docs/providers/heroku/r/app.html.markdown +++ b/website/source/docs/providers/heroku/r/app.html.markdown @@ -17,6 +17,7 @@ create and manage applications on Heroku. # Create a new Heroku app resource "heroku_app" "default" { name = "my-cool-app" + region = "us" config_vars { FOOBAR = "baz" From 6d3425f9619a56252e1eacd21a3a4f930208455c Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Tue, 31 Mar 2015 15:50:00 -0700 Subject: [PATCH 283/295] providers/heroku: Document environment variables --- website/source/docs/providers/heroku/index.html.markdown | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/website/source/docs/providers/heroku/index.html.markdown b/website/source/docs/providers/heroku/index.html.markdown index b04fd001c..696a41963 100644 --- a/website/source/docs/providers/heroku/index.html.markdown +++ b/website/source/docs/providers/heroku/index.html.markdown @@ -33,6 +33,8 @@ resource "heroku_app" "default" { The following arguments are supported: -* `api_key` - (Required) Heroku API token -* `email` - (Required) Email to be notified by Heroku +* `api_key` - (Required) Heroku API token. It must be provided, but it can also + be sourced from the `HEROKU_API_KEY` environment variable. +* `email` - (Required) Email to be notified by Heroku. It must be provided, but + it can also be sourced from the `HEROKU_EMAIL` environment variable. From 91a3d22a9f36b48348a1018e3518273576396b7c Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Tue, 31 Mar 2015 18:48:54 -0500 Subject: [PATCH 284/295] docs: resource addressing --- .../source/docs/commands/apply.html.markdown | 6 +- .../source/docs/commands/plan.html.markdown | 6 +- .../docs/commands/refresh.html.markdown | 6 +- .../resource-addressing.html.markdown | 57 +++++++++++++++++++ website/source/layouts/docs.erb | 4 ++ 5 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 website/source/docs/internals/resource-addressing.html.markdown diff --git a/website/source/docs/commands/apply.html.markdown b/website/source/docs/commands/apply.html.markdown index 6414f0741..9bb5acdbf 100644 --- a/website/source/docs/commands/apply.html.markdown +++ b/website/source/docs/commands/apply.html.markdown @@ -44,8 +44,10 @@ The command-line flags are all optional. The list of available flags are: * `-state-out=path` - Path to write updated state file. By default, the `-state` path will be used. -* `-target=resource` - Resource to target. Operation will be limited to this - resource and its dependencies. This flag can be used multiple times. +* `-target=resource` - A [Resource + Address](/docs/internals/resource-addressing.html) to target. Operation will + be limited to this resource and its dependencies. This flag can be used + multiple times. * `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag can be set multiple times. diff --git a/website/source/docs/commands/plan.html.markdown b/website/source/docs/commands/plan.html.markdown index fe5e90090..e05c460ce 100644 --- a/website/source/docs/commands/plan.html.markdown +++ b/website/source/docs/commands/plan.html.markdown @@ -45,8 +45,10 @@ The command-line flags are all optional. The list of available flags are: * `-state=path` - Path to the state file. Defaults to "terraform.tfstate". -* `-target=resource` - Resource to target. Operation will be limited to this - resource and its dependencies. This flag can be used multiple times. +* `-target=resource` - A [Resource + Address](/docs/internals/resource-addressing.html) to target. Operation will + be limited to this resource and its dependencies. This flag can be used + multiple times. * `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag can be set multiple times. diff --git a/website/source/docs/commands/refresh.html.markdown b/website/source/docs/commands/refresh.html.markdown index 6d15e1a40..0fc3fc938 100644 --- a/website/source/docs/commands/refresh.html.markdown +++ b/website/source/docs/commands/refresh.html.markdown @@ -36,8 +36,10 @@ The command-line flags are all optional. The list of available flags are: * `-state-out=path` - Path to write updated state file. By default, the `-state` path will be used. -* `-target=resource` - Resource to target. Operation will be limited to this - resource and its dependencies. This flag can be used multiple times. +* `-target=resource` - A [Resource + Address](/docs/internals/resource-addressing.html) to target. Operation will + be limited to this resource and its dependencies. This flag can be used + multiple times. * `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag can be set multiple times. diff --git a/website/source/docs/internals/resource-addressing.html.markdown b/website/source/docs/internals/resource-addressing.html.markdown new file mode 100644 index 000000000..b4b994a88 --- /dev/null +++ b/website/source/docs/internals/resource-addressing.html.markdown @@ -0,0 +1,57 @@ +--- +layout: "docs" +page_title: "Internals: Resource Address" +sidebar_current: "docs-internals-resource-addressing" +description: |- + Resource addressing is used to target specific resources in a larger + infrastructure. +--- + +# Resource Addressing + +A __Resource Address__ is a string that references a specific resource in a +larger infrastructure. The syntax of a resource address is: + +``` +.[optional fields] +``` + +Required fields: + + * `resource_type` - Type of the resource being addressed. + * `resource_name` - User-defined name of the resource. + +Optional fields may include: + + * `[N]` - where `N` is a `0`-based index into a resource with multiple + instances specified by the `count` meta-parameter. Omitting an index when + addressing a resource where `count > 1` means that the address references + all instances. + + +## Examples + +Given a Terraform config that includes: + +``` +resource "aws_instance" "web" { + # ... + count = 4 +} +``` + +An address like this: + + +``` +aws_instance.web[3] +``` + +Refers to only the last instance in the config, and an address like this: + +``` +aws_instance.web +``` + + +Refers to all four "web" instances. diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index a0b31127a..bbdecd4c8 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -215,6 +215,10 @@ > Resource Lifecycle + + > + Resource Addressing + From 9b3826cf368f01d8309b852df62e98466576103c Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Tue, 31 Mar 2015 16:23:20 -0700 Subject: [PATCH 285/295] providers/heroku: Add heroku-postgres to example --- website/source/docs/providers/heroku/r/addon.html.markdown | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/source/docs/providers/heroku/r/addon.html.markdown b/website/source/docs/providers/heroku/r/addon.html.markdown index d39cb1e8b..f9907597a 100644 --- a/website/source/docs/providers/heroku/r/addon.html.markdown +++ b/website/source/docs/providers/heroku/r/addon.html.markdown @@ -19,6 +19,12 @@ resource "heroku_app" "default" { name = "test-app" } +# Create a database, and configure the app to use it +resource "heroku_addon" "database" { + app = "${heroku_app.default.name}" + plan = "heroku-postgresql:hobby-basic" +} + # Add a web-hook addon for the app resource "heroku_addon" "webhook" { app = "${heroku_app.default.name}" From c0b85d4939b05e74a1e878ad72f3d6101561d029 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Wed, 1 Apr 2015 11:24:51 +0200 Subject: [PATCH 286/295] Use env var OS_POOL_NAME as default for pool attribute To have the same behaviour for openstack_networking_floatingip_v2 and openstack_compute_foatingip_v2. --- .../resource_openstack_networking_floatingip_v2.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go index cbc430764..fd8b3fc65 100644 --- a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2.go @@ -28,9 +28,10 @@ func resourceNetworkingFloatingIPV2() *schema.Resource { Computed: true, }, "pool": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + DefaultFunc: envDefaultFunc("OS_POOL_NAME"), }, }, } From 56aa764b947fac3a0bb94fedcc049b5aaeeb2e73 Mon Sep 17 00:00:00 2001 From: Guillaume Giamarchi Date: Wed, 1 Apr 2015 11:27:56 +0200 Subject: [PATCH 287/295] Add floating IP association in aceptance tests --- .../resource_openstack_compute_floatingip_v2_test.go | 9 +++++++-- ...source_openstack_networking_floatingip_v2_test.go | 12 +++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go index c246d1a51..a298a87d1 100644 --- a/builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_compute_floatingip_v2_test.go @@ -81,6 +81,11 @@ func testAccCheckComputeV2FloatingIPExists(t *testing.T, n string, kp *floatingi } } -var testAccComputeV2FloatingIP_basic = fmt.Sprintf(` +var testAccComputeV2FloatingIP_basic = ` resource "openstack_compute_floatingip_v2" "foo" { - }`) + } + + resource "openstack_compute_instance_v2" "bar" { + name = "terraform-acc-floating-ip-test" + floating_ip = "${openstack_compute_floatingip_v2.foo.address}" + }` diff --git a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go index cd08ea512..5c8ae38e3 100644 --- a/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go +++ b/builtin/providers/openstack/resource_openstack_networking_floatingip_v2_test.go @@ -81,9 +81,11 @@ func testAccCheckNetworkingV2FloatingIPExists(t *testing.T, n string, kp *floati } } -var testAccNetworkingV2FloatingIP_basic = fmt.Sprintf(` +var testAccNetworkingV2FloatingIP_basic = ` resource "openstack_networking_floatingip_v2" "foo" { - region = "%s" - pool = "%s" - }`, - OS_REGION_NAME, OS_POOL_NAME) + } + + resource "openstack_compute_instance_v2" "bar" { + name = "terraform-acc-floating-ip-test" + floating_ip = "${openstack_networking_floatingip_v2.foo.address}" + }` From dc67b043fac9c4d45066e56bd8e0a00e33015a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Guminiak?= Date: Wed, 1 Apr 2015 14:33:08 +0200 Subject: [PATCH 288/295] Avoid panics when DBName is not set provider/aws: The DBName in RDS instance is optional and when not set, the Read function return null. --- builtin/providers/aws/resource_aws_db_instance.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index e99744a0f..11b05c81b 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -304,7 +304,11 @@ func resourceAwsDbInstanceRead(d *schema.ResourceData, meta interface{}) error { return nil } - d.Set("name", *v.DBName) + if v.DBName != nil { + d.Set("name", *v.DBName) + } else { + d.Set("name", "") + } d.Set("username", *v.MasterUsername) d.Set("engine", *v.Engine) d.Set("engine_version", *v.EngineVersion) From 27f0873de7c24cf768d2ede9dec81c82830c6885 Mon Sep 17 00:00:00 2001 From: Phil Frost Date: Wed, 1 Apr 2015 08:57:50 -0400 Subject: [PATCH 289/295] Don't error when reading s3 bucket with no tags s3.GetBucketTagging returns an error if there are no tags associated with a bucket. Consequently, any configuration with a tagless s3 bucket would fail with an error, "the TagSet does not exist". Handle that error more appropriately, interpreting it as an empty set of tags. --- .../providers/aws/resource_aws_s3_bucket.go | 6 ++---- builtin/providers/aws/s3_tags.go | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket.go b/builtin/providers/aws/resource_aws_s3_bucket.go index cb32d5fa3..a33f9b35f 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket.go +++ b/builtin/providers/aws/resource_aws_s3_bucket.go @@ -88,14 +88,12 @@ func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error { return err } - resp, err := s3conn.GetBucketTagging(&s3.GetBucketTaggingRequest{ - Bucket: aws.String(d.Id()), - }) + tagSet, err := getTagSetS3(s3conn, d.Id()) if err != nil { return err } - if err := d.Set("tags", tagsToMapS3(resp.TagSet)); err != nil { + if err := d.Set("tags", tagsToMapS3(tagSet)); err != nil { return err } diff --git a/builtin/providers/aws/s3_tags.go b/builtin/providers/aws/s3_tags.go index 43678952b..4b8234b9b 100644 --- a/builtin/providers/aws/s3_tags.go +++ b/builtin/providers/aws/s3_tags.go @@ -110,3 +110,22 @@ func tagsToMapS3(ts []s3.Tag) map[string]string { return result } + +// return a slice of s3 tags associated with the given s3 bucket. Essentially +// s3.GetBucketTagging, except returns an empty slice instead of an error when +// there are no tags. +func getTagSetS3(s3conn *s3.S3, bucket string) ([]s3.Tag, error) { + request := &s3.GetBucketTaggingRequest{ + Bucket: aws.String(bucket), + } + + response, err := s3conn.GetBucketTagging(request) + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "NoSuchTagSet" { + // There is no tag set associated with the bucket. + return []s3.Tag{}, nil + } else if err != nil { + return nil, err + } + + return response.TagSet, nil +} From 3d659982828a706006c73c746dc8f8c2df73c572 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 1 Apr 2015 09:24:26 -0500 Subject: [PATCH 290/295] provider/aws: Fix issue with tainted ASG groups failing to re-create --- builtin/providers/aws/resource_aws_autoscaling_group.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_autoscaling_group.go b/builtin/providers/aws/resource_aws_autoscaling_group.go index de3bbe9cc..60b22ff26 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group.go @@ -287,7 +287,12 @@ func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{}) return err } - return nil + return resource.Retry(5*time.Minute, func() error { + if g, _ = getAwsAutoscalingGroup(d, meta); g != nil { + return fmt.Errorf("Auto Scaling Group still exists") + } + return nil + }) } func getAwsAutoscalingGroup( From 84e448de1a5c58c97c96a6d8c0ce59331eafb1d5 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 1 Apr 2015 10:42:53 -0500 Subject: [PATCH 291/295] Fix hashcode for ASG test --- builtin/providers/aws/resource_aws_autoscaling_group_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_autoscaling_group_test.go b/builtin/providers/aws/resource_aws_autoscaling_group_test.go index 661e71fe8..09a4d73a6 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group_test.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group_test.go @@ -26,7 +26,7 @@ func TestAccAWSAutoScalingGroup_basic(t *testing.T) { testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), testAccCheckAWSAutoScalingGroupAttributes(&group), resource.TestCheckResourceAttr( - "aws_autoscaling_group.bar", "availability_zones.1807834199", "us-west-2a"), + "aws_autoscaling_group.bar", "availability_zones.2487133097", "us-west-2a"), resource.TestCheckResourceAttr( "aws_autoscaling_group.bar", "name", "foobar3-terraform-test"), resource.TestCheckResourceAttr( From 816c4b475fd0f1a159229562b7b7c10ceaa518de Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Wed, 1 Apr 2015 11:11:19 -0500 Subject: [PATCH 292/295] core: [tests] fix order dependent test --- terraform/context_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terraform/context_test.go b/terraform/context_test.go index f582ae679..b9de8d79d 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -1579,6 +1579,8 @@ func TestContext2Refresh_targetedCount(t *testing.T) { "aws_instance.me.1", "aws_instance.me.2", } + sort.Strings(expected) + sort.Strings(refreshedResources) if !reflect.DeepEqual(refreshedResources, expected) { t.Fatalf("expected: %#v, got: %#v", expected, refreshedResources) } From 815b79753a21582d124642f44502f73e96bdd277 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 1 Apr 2015 14:49:50 -0500 Subject: [PATCH 293/295] return error if failed to set tags on Route 53 zone --- builtin/providers/aws/resource_aws_route53_zone.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_route53_zone.go b/builtin/providers/aws/resource_aws_route53_zone.go index e6c8be571..b60c91a79 100644 --- a/builtin/providers/aws/resource_aws_route53_zone.go +++ b/builtin/providers/aws/resource_aws_route53_zone.go @@ -105,7 +105,10 @@ func resourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error if resp.ResourceTagSet != nil { tags = resp.ResourceTagSet.Tags } - d.Set("tags", tagsToMapR53(tags)) + + if err := d.Set("tags", tagsToMapR53(tags)); err != nil { + return err + } return nil } From b31a69fe43dd3525ff87313ddef87dc206b3c999 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 1 Apr 2015 16:05:19 -0500 Subject: [PATCH 294/295] provider/aws: Allow DB Parameter group to change in RDS --- builtin/providers/aws/resource_aws_db_instance.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/builtin/providers/aws/resource_aws_db_instance.go b/builtin/providers/aws/resource_aws_db_instance.go index 0cf2e4202..4b90b79ba 100644 --- a/builtin/providers/aws/resource_aws_db_instance.go +++ b/builtin/providers/aws/resource_aws_db_instance.go @@ -170,7 +170,6 @@ func resourceAwsDbInstance() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - ForceNew: true, }, "address": &schema.Schema{ @@ -456,6 +455,12 @@ func resourceAwsDbInstanceUpdate(d *schema.ResourceData, meta interface{}) error req.MultiAZ = aws.Boolean(d.Get("multi_az").(bool)) } + if d.HasChange("parameter_group_name") { + change = true + d.SetPartial("parameter_group_name") + req.DBParameterGroupName = aws.String(d.Get("parameter_group_name").(string)) + } + if change { log.Printf("[DEBUG] DB Instance Modification request: %#v", req) _, err := conn.ModifyDBInstance(req) From e2f3b0753598af8f66d59f1898978e45a79607a7 Mon Sep 17 00:00:00 2001 From: Clint Date: Wed, 1 Apr 2015 16:11:27 -0500 Subject: [PATCH 295/295] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24336b5ee..19332c01d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,9 @@ IMPROVEMENTS: `www` instead of `www.example.com`. * providers/aws: Improve dependency violation error handling, when deleting Internet Gateways or Auto Scaling groups [GH-1325]. + * provider/aws: Add non-destructive updates to AWS RDS. You can now upgrade + `egine_version`, `parameter_group_name`, and `multi_az` without forcing + a new database to be created.[GH-1341] BUG FIXES: