From ed98e02e4ae94df69c70baded957fd4fba991a5c Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 14 Jul 2015 10:19:10 -0500 Subject: [PATCH 1/3] 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}" +} +` From 55a1a31c2bcab068f4ee524a242309e4ea77fce3 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 14 Jul 2015 10:24:16 -0500 Subject: [PATCH 2/3] provider/aws: document that availability zones are now optional in ASGs --- .../docs/providers/aws/r/autoscaling_group.html.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/source/docs/providers/aws/r/autoscaling_group.html.markdown b/website/source/docs/providers/aws/r/autoscaling_group.html.markdown index 552ff8d57..73715ee91 100644 --- a/website/source/docs/providers/aws/r/autoscaling_group.html.markdown +++ b/website/source/docs/providers/aws/r/autoscaling_group.html.markdown @@ -45,7 +45,8 @@ The following arguments are supported: * `max_size` - (Required) The maximum size of the auto scale group. * `min_size` - (Required) The minimum size of the auto scale group. (See also [Waiting for Capacity](#waiting-for-capacity) below.) -* `availability_zones` - (Required) A list of AZs to launch resources in. +* `availability_zones` - (Optional) A list of AZs to launch resources in. + Required only if you do not specify any `vpc_zone_identifier` * `launch_configuration` - (Required) The ID of the launch configuration to use. * `health_check_grace_period` - (Optional) Time after instance comes into service before checking health. * `health_check_type` - (Optional) "EC2" or "ELB". Controls how health checking is done. From 04a5890853ecd438f1b6fc0cb0d437c96459f419 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 14 Jul 2015 10:39:31 -0500 Subject: [PATCH 3/3] minor fix to the test --- .../providers/aws/resource_aws_autoscaling_group_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/builtin/providers/aws/resource_aws_autoscaling_group_test.go b/builtin/providers/aws/resource_aws_autoscaling_group_test.go index a090b72f2..9f26bb7f2 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group_test.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group_test.go @@ -321,11 +321,12 @@ func testAccCheckAWSAutoScalingGroupAttributesVPCZoneIdentifer(group *autoscalin subnets = append(subnets, rs.Primary.Attributes["id"]) } - if group.VPCZoneIdentifier == nil || *group.VPCZoneIdentifier != "" { + if group.VPCZoneIdentifier == nil { 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 { @@ -516,7 +517,7 @@ resource "aws_launch_configuration" "foobar" { resource "aws_autoscaling_group" "bar" { availability_zones = ["us-west-2a"] name = "vpc-asg-test" - max_size = 1 + max_size = 2 min_size = 1 health_check_grace_period = 300 health_check_type = "ELB" @@ -565,7 +566,7 @@ resource "aws_autoscaling_group" "bar" { "${aws_subnet.alt.id}", ] name = "vpc-asg-test" - max_size = 1 + max_size = 2 min_size = 1 health_check_grace_period = 300 health_check_type = "ELB"