diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index a357e5b1d..8bc9adab5 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -16,6 +16,8 @@ import ( "github.com/hashicorp/aws-sdk-go/gen/rds" "github.com/hashicorp/aws-sdk-go/gen/route53" "github.com/hashicorp/aws-sdk-go/gen/s3" + + awsEC2 "github.com/hashicorp/aws-sdk-go/gen/ec2" ) type Config struct { @@ -27,6 +29,7 @@ type Config struct { type AWSClient struct { ec2conn *ec2.EC2 + awsEC2conn *awsEC2.EC2 elbconn *elb.ELB autoscalingconn *autoscaling.AutoScaling s3conn *s3.S3 @@ -77,6 +80,8 @@ func (c *Config) Client() (interface{}, error) { // See http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html log.Println("[INFO] Initializing Route53 connection") client.r53conn = route53.New(creds, "us-east-1", nil) + log.Println("[INFO] Initializing AWS-GO EC2 Connection") + client.awsEC2conn = awsEC2.New(creds, c.Region, nil) } if len(errs) > 0 { diff --git a/builtin/providers/aws/resource_aws_subnet.go b/builtin/providers/aws/resource_aws_subnet.go index 743c22f64..fd7ac2e06 100644 --- a/builtin/providers/aws/resource_aws_subnet.go +++ b/builtin/providers/aws/resource_aws_subnet.go @@ -5,9 +5,10 @@ import ( "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" - "github.com/mitchellh/goamz/ec2" ) func resourceAwsSubnet() *schema.Resource { @@ -50,12 +51,12 @@ func resourceAwsSubnet() *schema.Resource { } func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).ec2conn + ec2conn := meta.(*AWSClient).awsEC2conn - createOpts := &ec2.CreateSubnet{ - AvailabilityZone: d.Get("availability_zone").(string), - CidrBlock: d.Get("cidr_block").(string), - VpcId: d.Get("vpc_id").(string), + createOpts := &ec2.CreateSubnetRequest{ + AvailabilityZone: aws.String(d.Get("availability_zone").(string)), + CIDRBlock: aws.String(d.Get("cidr_block").(string)), + VPCID: aws.String(d.Get("vpc_id").(string)), } resp, err := ec2conn.CreateSubnet(createOpts) @@ -65,16 +66,16 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error { } // Get the ID and store it - subnet := &resp.Subnet - d.SetId(subnet.SubnetId) - log.Printf("[INFO] Subnet ID: %s", subnet.SubnetId) + subnet := resp.Subnet + d.SetId(*subnet.SubnetID) + log.Printf("[INFO] Subnet ID: %s", subnet.SubnetID) // Wait for the Subnet to become available - log.Printf("[DEBUG] Waiting for subnet (%s) to become available", subnet.SubnetId) + log.Printf("[DEBUG] Waiting for subnet (%s) to become available", subnet.SubnetID) stateConf := &resource.StateChangeConf{ Pending: []string{"pending"}, Target: "available", - Refresh: SubnetStateRefreshFunc(ec2conn, subnet.SubnetId), + Refresh: SubnetStateRefreshFunc(ec2conn, *subnet.SubnetID), Timeout: 10 * time.Minute, } @@ -90,12 +91,14 @@ func resourceAwsSubnetCreate(d *schema.ResourceData, meta interface{}) error { } func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).ec2conn + ec2conn := meta.(*AWSClient).awsEC2conn - resp, err := ec2conn.DescribeSubnets([]string{d.Id()}, ec2.NewFilter()) + resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsRequest{ + SubnetIDs: []string{d.Id()}, + }) if err != nil { - if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidSubnetID.NotFound" { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidSubnetID.NotFound" { // Update state to indicate the subnet no longer exists. d.SetId("") return nil @@ -108,35 +111,35 @@ func resourceAwsSubnetRead(d *schema.ResourceData, meta interface{}) error { subnet := &resp.Subnets[0] - d.Set("vpc_id", subnet.VpcId) + d.Set("vpc_id", subnet.VPCID) d.Set("availability_zone", subnet.AvailabilityZone) - d.Set("cidr_block", subnet.CidrBlock) - d.Set("map_public_ip_on_launch", subnet.MapPublicIpOnLaunch) - d.Set("tags", tagsToMap(subnet.Tags)) + d.Set("cidr_block", subnet.CIDRBlock) + d.Set("map_public_ip_on_launch", subnet.MapPublicIPOnLaunch) + d.Set("tags", tagsToMapSDK(subnet.Tags)) return nil } func resourceAwsSubnetUpdate(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).ec2conn + ec2conn := meta.(*AWSClient).awsEC2conn d.Partial(true) - if err := setTags(ec2conn, d); err != nil { + if err := setTagsSDK(ec2conn, d); err != nil { return err } else { d.SetPartial("tags") } if d.HasChange("map_public_ip_on_launch") { - modifyOpts := &ec2.ModifySubnetAttribute{ - SubnetId: d.Id(), - MapPublicIpOnLaunch: true, + modifyOpts := &ec2.ModifySubnetAttributeRequest{ + SubnetID: aws.String(d.Id()), + MapPublicIPOnLaunch: &ec2.AttributeBooleanValue{aws.Boolean(true)}, } log.Printf("[DEBUG] Subnet modify attributes: %#v", modifyOpts) - _, err := ec2conn.ModifySubnetAttribute(modifyOpts) + err := ec2conn.ModifySubnetAttribute(modifyOpts) if err != nil { return err @@ -151,11 +154,16 @@ func resourceAwsSubnetUpdate(d *schema.ResourceData, meta interface{}) error { } func resourceAwsSubnetDelete(d *schema.ResourceData, meta interface{}) error { - ec2conn := meta.(*AWSClient).ec2conn + ec2conn := meta.(*AWSClient).awsEC2conn log.Printf("[INFO] Deleting subnet: %s", d.Id()) - if _, err := ec2conn.DeleteSubnet(d.Id()); err != nil { - ec2err, ok := err.(*ec2.Error) + + err := ec2conn.DeleteSubnet(&ec2.DeleteSubnetRequest{ + SubnetID: aws.String(d.Id()), + }) + + if err != nil { + ec2err, ok := err.(aws.APIError) if ok && ec2err.Code == "InvalidSubnetID.NotFound" { return nil } @@ -169,9 +177,11 @@ func resourceAwsSubnetDelete(d *schema.ResourceData, meta interface{}) error { // SubnetStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a Subnet. func SubnetStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { return func() (interface{}, string, error) { - resp, err := conn.DescribeSubnets([]string{id}, ec2.NewFilter()) + resp, err := conn.DescribeSubnets(&ec2.DescribeSubnetsRequest{ + SubnetIDs: []string{id}, + }) if err != nil { - if ec2err, ok := err.(*ec2.Error); ok && ec2err.Code == "InvalidSubnetID.NotFound" { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidSubnetID.NotFound" { resp = nil } else { log.Printf("Error on SubnetStateRefresh: %s", err) @@ -186,6 +196,6 @@ func SubnetStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc } subnet := &resp.Subnets[0] - return subnet, subnet.State, nil + return subnet, *subnet.State, nil } } diff --git a/builtin/providers/aws/resource_aws_subnet_test.go b/builtin/providers/aws/resource_aws_subnet_test.go index 97cca8e07..d06b5b31e 100644 --- a/builtin/providers/aws/resource_aws_subnet_test.go +++ b/builtin/providers/aws/resource_aws_subnet_test.go @@ -4,21 +4,22 @@ 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" - "github.com/mitchellh/goamz/ec2" ) func TestAccAWSSubnet(t *testing.T) { var v ec2.Subnet testCheck := func(*terraform.State) error { - if v.CidrBlock != "10.1.1.0/24" { - return fmt.Errorf("bad cidr: %s", v.CidrBlock) + if *v.CIDRBlock != "10.1.1.0/24" { + return fmt.Errorf("bad cidr: %s", v.CIDRBlock) } - if v.MapPublicIpOnLaunch != true { - return fmt.Errorf("bad MapPublicIpOnLaunch: %t", v.MapPublicIpOnLaunch) + if *v.MapPublicIPOnLaunch != true { + return fmt.Errorf("bad MapPublicIpOnLaunch: %t", v.MapPublicIPOnLaunch) } return nil @@ -42,7 +43,7 @@ func TestAccAWSSubnet(t *testing.T) { } func testAccCheckSubnetDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).ec2conn + conn := testAccProvider.Meta().(*AWSClient).awsEC2conn for _, rs := range s.RootModule().Resources { if rs.Type != "aws_subnet" { @@ -50,8 +51,9 @@ func testAccCheckSubnetDestroy(s *terraform.State) error { } // Try to find the resource - resp, err := conn.DescribeSubnets( - []string{rs.Primary.ID}, ec2.NewFilter()) + resp, err := conn.DescribeSubnets(&ec2.DescribeSubnetsRequest{ + SubnetIDs: []string{rs.Primary.ID}, + }) if err == nil { if len(resp.Subnets) > 0 { return fmt.Errorf("still exist.") @@ -61,7 +63,7 @@ func testAccCheckSubnetDestroy(s *terraform.State) error { } // Verify the error is what we want - ec2err, ok := err.(*ec2.Error) + ec2err, ok := err.(aws.APIError) if !ok { return err } @@ -84,9 +86,10 @@ func testAccCheckSubnetExists(n string, v *ec2.Subnet) resource.TestCheckFunc { return fmt.Errorf("No ID is set") } - conn := testAccProvider.Meta().(*AWSClient).ec2conn - resp, err := conn.DescribeSubnets( - []string{rs.Primary.ID}, ec2.NewFilter()) + conn := testAccProvider.Meta().(*AWSClient).awsEC2conn + resp, err := conn.DescribeSubnets(&ec2.DescribeSubnetsRequest{ + SubnetIDs: []string{rs.Primary.ID}, + }) if err != nil { return err } diff --git a/builtin/providers/aws/tags_sdk.go b/builtin/providers/aws/tags_sdk.go new file mode 100644 index 000000000..7e9690b78 --- /dev/null +++ b/builtin/providers/aws/tags_sdk.go @@ -0,0 +1,106 @@ +package aws + +// TODO: Clint: consolidate tags and tags_sdk +// tags_sdk and tags_sdk_test are used only for transition to aws-sdk-go +// and will replace tags and tags_test when the transition to aws-sdk-go/ec2 is +// complete + +import ( + "log" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/ec2" + "github.com/hashicorp/terraform/helper/schema" +) + +// tagsSchema returns the schema to use for tags. +// +// TODO: uncomment this when we replace the original tags.go +// +// func tagsSchema() *schema.Schema { +// return &schema.Schema{ +// Type: schema.TypeMap, +// Optional: true, +// } +// } + +// setTags is a helper to set the tags for a resource. It expects the +// tags field to be named "tags" +func setTagsSDK(conn *ec2.EC2, 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 := diffTagsSDK(tagsFromMapSDK(o), tagsFromMapSDK(n)) + + // Set tags + if len(remove) > 0 { + log.Printf("[DEBUG] Removing tags: %#v", remove) + err := conn.DeleteTags(&ec2.DeleteTagsRequest{ + Resources: []string{d.Id()}, + Tags: remove, + }) + if err != nil { + return err + } + } + if len(create) > 0 { + log.Printf("[DEBUG] Creating tags: %#v", create) + err := conn.CreateTags(&ec2.CreateTagsRequest{ + Resources: []string{d.Id()}, + 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 diffTagsSDK(oldTags, newTags []ec2.Tag) ([]ec2.Tag, []ec2.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 []ec2.Tag + for _, t := range oldTags { + old, ok := create[*t.Key] + if !ok || old != *t.Value { + // Delete it! + remove = append(remove, t) + } + } + + return tagsFromMapSDK(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMapSDK(m map[string]interface{}) []ec2.Tag { + result := make([]ec2.Tag, 0, len(m)) + for k, v := range m { + result = append(result, ec2.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + }) + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMapSDK(ts []ec2.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_sdk_test.go b/builtin/providers/aws/tags_sdk_test.go new file mode 100644 index 000000000..5a5b0e600 --- /dev/null +++ b/builtin/providers/aws/tags_sdk_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 TestDiffTagsSDK(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 := diffTagsSDK(tagsFromMapSDK(tc.Old), tagsFromMapSDK(tc.New)) + cm := tagsToMapSDK(c) + rm := tagsToMapSDK(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 testAccCheckTagsSDK( + ts *[]ec2.Tag, key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + m := tagsToMapSDK(*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 + } +}