From ed98e02e4ae94df69c70baded957fd4fba991a5c Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 14 Jul 2015 10:19:10 -0500 Subject: [PATCH] provider/aws: Improved Auto Scaling Groups updates - availability zones are optional if you specify a VPC Zone Identifier (subnet) - availability zones can be updated in place --- .../aws/resource_aws_autoscaling_group.go | 36 ++-- .../resource_aws_autoscaling_group_test.go | 154 ++++++++++++++++++ 2 files changed, 179 insertions(+), 11 deletions(-) diff --git a/builtin/providers/aws/resource_aws_autoscaling_group.go b/builtin/providers/aws/resource_aws_autoscaling_group.go index 279e76122..ae6c94b33 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group.go @@ -91,8 +91,7 @@ func resourceAwsAutoscalingGroup() *schema.Resource { "availability_zones": &schema.Schema{ Type: schema.TypeSet, - Required: true, - ForceNew: true, + Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, @@ -108,7 +107,6 @@ func resourceAwsAutoscalingGroup() *schema.Resource { Type: schema.TypeSet, Optional: true, Computed: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, }, @@ -135,8 +133,11 @@ func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) autoScalingGroupOpts.LaunchConfigurationName = aws.String(d.Get("launch_configuration").(string)) autoScalingGroupOpts.MinSize = aws.Long(int64(d.Get("min_size").(int))) autoScalingGroupOpts.MaxSize = aws.Long(int64(d.Get("max_size").(int))) - autoScalingGroupOpts.AvailabilityZones = expandStringList( - d.Get("availability_zones").(*schema.Set).List()) + + // Availability Zones are optional if VPC Zone Identifer(s) are specified + if v, ok := d.GetOk("availability_zones"); ok && v.(*schema.Set).Len() > 0 { + autoScalingGroupOpts.AvailabilityZones = expandStringList(v.(*schema.Set).List()) + } if v, ok := d.GetOk("tag"); ok { autoScalingGroupOpts.Tags = autoscalingTagsFromMap( @@ -165,12 +166,7 @@ func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) } if v, ok := d.GetOk("vpc_zone_identifier"); ok && v.(*schema.Set).Len() > 0 { - exp := expandStringList(v.(*schema.Set).List()) - strs := make([]string, len(exp)) - for _, s := range exp { - strs = append(strs, *s) - } - autoScalingGroupOpts.VPCZoneIdentifier = aws.String(strings.Join(strs, ",")) + autoScalingGroupOpts.VPCZoneIdentifier = expandVpcZoneIdentifiers(v.(*schema.Set).List()) } if v, ok := d.GetOk("termination_policies"); ok && v.(*schema.Set).Len() > 0 { @@ -256,6 +252,16 @@ func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{}) opts.HealthCheckType = aws.String(d.Get("health_check_type").(string)) } + if d.HasChange("vpc_zone_identifier") { + opts.VPCZoneIdentifier = expandVpcZoneIdentifiers(d.Get("vpc_zone_identifier").(*schema.Set).List()) + } + + if d.HasChange("availability_zones") { + if v, ok := d.GetOk("availability_zones"); ok && v.(*schema.Set).Len() > 0 { + opts.AvailabilityZones = expandStringList(d.Get("availability_zones").(*schema.Set).List()) + } + } + if err := setAutoscalingTags(conn, d); err != nil { return err } else { @@ -538,3 +544,11 @@ func getLBInstanceStates(g *autoscaling.Group, meta interface{}) (map[string]map return lbInstanceStates, nil } + +func expandVpcZoneIdentifiers(list []interface{}) *string { + strs := make([]string, len(list)) + for _, s := range list { + strs = append(strs, s.(string)) + } + return aws.String(strings.Join(strs, ",")) +} diff --git a/builtin/providers/aws/resource_aws_autoscaling_group_test.go b/builtin/providers/aws/resource_aws_autoscaling_group_test.go index cc3cdf6e5..a090b72f2 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group_test.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group_test.go @@ -101,6 +101,32 @@ func TestAccAWSAutoScalingGroup_tags(t *testing.T) { }) } +func TestAccAWSAutoScalingGroup_VpcUpdates(t *testing.T) { + var group autoscaling.Group + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSAutoScalingGroupDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSAutoScalingGroupConfigWithAZ, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), + ), + }, + + resource.TestStep{ + Config: testAccAWSAutoScalingGroupConfigWithVPCIdent, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), + testAccCheckAWSAutoScalingGroupAttributesVPCZoneIdentifer(&group), + ), + }, + }, + }) +} + func TestAccAWSAutoScalingGroup_WithLoadBalancer(t *testing.T) { var group autoscaling.Group @@ -284,6 +310,39 @@ func testAccCheckAWSAutoScalingGroupHealthyCapacity( } } +func testAccCheckAWSAutoScalingGroupAttributesVPCZoneIdentifer(group *autoscaling.Group) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Grab Subnet Ids + var subnets []string + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_subnet" { + continue + } + subnets = append(subnets, rs.Primary.Attributes["id"]) + } + + if group.VPCZoneIdentifier == nil || *group.VPCZoneIdentifier != "" { + return fmt.Errorf("Bad VPC Zone Identifier\nexpected: %s\ngot nil", subnets) + } + + zones := strings.Split(*group.VPCZoneIdentifier, ",") + remaining := len(zones) + for _, z := range zones { + for _, s := range subnets { + if z == s { + remaining-- + } + } + } + + if remaining != 0 { + return fmt.Errorf("Bad VPC Zone Identifier match\nexpected: %s\ngot:%s", zones, subnets) + } + + return nil + } +} + const testAccAWSAutoScalingGroupConfig = ` resource "aws_launch_configuration" "foobar" { image_id = "ami-21f78e11" @@ -421,3 +480,98 @@ resource "aws_autoscaling_group" "bar" { load_balancers = ["${aws_elb.bar.name}"] } ` + +const testAccAWSAutoScalingGroupConfigWithAZ = ` +resource "aws_vpc" "default" { + cidr_block = "10.0.0.0/16" + tags { + Name = "terraform-test" + } +} + +resource "aws_subnet" "main" { + vpc_id = "${aws_vpc.default.id}" + cidr_block = "10.0.1.0/24" + availability_zone = "us-west-2a" + tags { + Name = "terraform-test" + } +} + +resource "aws_subnet" "alt" { + vpc_id = "${aws_vpc.default.id}" + cidr_block = "10.0.2.0/24" + availability_zone = "us-west-2b" + tags { + Name = "asg-vpc-thing" + } +} + +resource "aws_launch_configuration" "foobar" { + name = "vpc-asg-test" + image_id = "ami-b5b3fc85" + instance_type = "t2.micro" +} + +resource "aws_autoscaling_group" "bar" { + availability_zones = ["us-west-2a"] + name = "vpc-asg-test" + max_size = 1 + min_size = 1 + health_check_grace_period = 300 + health_check_type = "ELB" + desired_capacity = 1 + force_delete = true + termination_policies = ["OldestInstance"] + launch_configuration = "${aws_launch_configuration.foobar.name}" +} +` + +const testAccAWSAutoScalingGroupConfigWithVPCIdent = ` +resource "aws_vpc" "default" { + cidr_block = "10.0.0.0/16" + tags { + Name = "terraform-test" + } +} + +resource "aws_subnet" "main" { + vpc_id = "${aws_vpc.default.id}" + cidr_block = "10.0.1.0/24" + availability_zone = "us-west-2a" + tags { + Name = "terraform-test" + } +} + +resource "aws_subnet" "alt" { + vpc_id = "${aws_vpc.default.id}" + cidr_block = "10.0.2.0/24" + availability_zone = "us-west-2b" + tags { + Name = "asg-vpc-thing" + } +} + +resource "aws_launch_configuration" "foobar" { + name = "vpc-asg-test" + image_id = "ami-b5b3fc85" + instance_type = "t2.micro" +} + +resource "aws_autoscaling_group" "bar" { + vpc_zone_identifier = [ + "${aws_subnet.main.id}", + "${aws_subnet.alt.id}", + ] + name = "vpc-asg-test" + max_size = 1 + min_size = 1 + health_check_grace_period = 300 + health_check_type = "ELB" + desired_capacity = 1 + force_delete = true + termination_policies = ["OldestInstance"] + launch_configuration = "${aws_launch_configuration.foobar.name}" +} +`