diff --git a/builtin/providers/aws/autoscaling_tags.go b/builtin/providers/aws/autoscaling_tags.go index e9ca8531aedf..a86327c72d6c 100644 --- a/builtin/providers/aws/autoscaling_tags.go +++ b/builtin/providers/aws/autoscaling_tags.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "regexp" + "strconv" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/autoscaling" @@ -12,8 +13,8 @@ import ( "github.com/hashicorp/terraform/helper/schema" ) -// tagsSchema returns the schema to use for tags. -func autoscalingTagsSchema() *schema.Schema { +// autoscalingTagSchema returns the schema to use for the tag element. +func autoscalingTagSchema() *schema.Schema { return &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -35,11 +36,11 @@ func autoscalingTagsSchema() *schema.Schema { }, }, }, - Set: autoscalingTagsToHash, + Set: autoscalingTagToHash, } } -func autoscalingTagsToHash(v interface{}) int { +func autoscalingTagToHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) buf.WriteString(fmt.Sprintf("%s-", m["key"].(string))) @@ -52,35 +53,55 @@ func autoscalingTagsToHash(v interface{}) int { // 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") { + resourceID := d.Get("name").(string) + var createTags, removeTags []*autoscaling.Tag + + if d.HasChange("tag") || d.HasChange("tags") { 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.CreateOrUpdateTagsInput{ - Tags: c, - } + + createTags = append(createTags, c...) + removeTags = append(removeTags, r...) + + oraw, nraw = d.GetChange("tags") + + c, r = diffAutoscalingTags( + autoscalingTagsFromList(oraw.([]interface{}), resourceID), + autoscalingTagsFromList(nraw.([]interface{}), resourceID), + resourceID) + + createTags = append(createTags, c...) + removeTags = append(removeTags, r...) + } + + // Set tags + if len(removeTags) > 0 { + log.Printf("[DEBUG] Removing autoscaling tags: %#v", removeTags) + remove := autoscaling.DeleteTagsInput{ - Tags: r, + Tags: removeTags, } - // Set tags - if len(r) > 0 { - log.Printf("[DEBUG] Removing autoscaling tags: %#v", r) - if _, err := conn.DeleteTags(&remove); err != nil { - return err - } + 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 - } + } + + if len(createTags) > 0 { + log.Printf("[DEBUG] Creating autoscaling tags: %#v", createTags) + + create := autoscaling.CreateOrUpdateTagsInput{ + Tags: createTags, + } + + if _, err := conn.CreateOrUpdateTags(&create); err != nil { + return err } } @@ -95,6 +116,7 @@ func diffAutoscalingTags(oldTags, newTags []*autoscaling.Tag, resourceID string) create := make(map[string]interface{}) for _, t := range newTags { tag := map[string]interface{}{ + "key": *t.Key, "value": *t.Value, "propagate_at_launch": *t.PropagateAtLaunch, } @@ -115,19 +137,24 @@ func diffAutoscalingTags(oldTags, newTags []*autoscaling.Tag, resourceID string) return autoscalingTagsFromMap(create, resourceID), remove } +func autoscalingTagsFromList(vs []interface{}, resourceID string) []*autoscaling.Tag { + result := make([]*autoscaling.Tag, 0, len(vs)) + for _, tag := range vs { + attr := tag.(map[string]interface{}) + if t := autoscalingTagFromMap(attr, resourceID); t != nil { + result = append(result, t) + } + } + return result +} + // 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 { + for _, v := range m { attr := v.(map[string]interface{}) - t := &autoscaling.Tag{ - Key: aws.String(k), - Value: aws.String(attr["value"].(string)), - PropagateAtLaunch: aws.Bool(attr["propagate_at_launch"].(bool)), - ResourceId: aws.String(resourceID), - ResourceType: aws.String("auto-scaling-group"), - } - if !tagIgnoredAutoscaling(t) { + t := autoscalingTagFromMap(attr, resourceID) + if t != nil { result = append(result, t) } } @@ -135,11 +162,38 @@ func autoscalingTagsFromMap(m map[string]interface{}, resourceID string) []*auto return result } +func autoscalingTagFromMap(attr map[string]interface{}, resourceID string) *autoscaling.Tag { + var propagate_at_launch bool + + if v, ok := attr["propagate_at_launch"].(bool); ok { + propagate_at_launch = v + } + + if v, ok := attr["propagate_at_launch"].(string); ok { + propagate_at_launch, _ = strconv.ParseBool(v) + } + + t := &autoscaling.Tag{ + Key: aws.String(attr["key"].(string)), + Value: aws.String(attr["value"].(string)), + PropagateAtLaunch: aws.Bool(propagate_at_launch), + ResourceId: aws.String(resourceID), + ResourceType: aws.String("auto-scaling-group"), + } + + if tagIgnoredAutoscaling(t) { + return nil + } + + return t +} + // 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{}{ + "key": *t.Key, "value": *t.Value, "propagate_at_launch": *t.PropagateAtLaunch, } @@ -154,6 +208,7 @@ func autoscalingTagDescriptionsToMap(ts *[]*autoscaling.TagDescription) map[stri tags := make(map[string]map[string]interface{}) for _, t := range *ts { tag := map[string]interface{}{ + "key": *t.Key, "value": *t.Value, "propagate_at_launch": *t.PropagateAtLaunch, } diff --git a/builtin/providers/aws/autoscaling_tags_test.go b/builtin/providers/aws/autoscaling_tags_test.go index 04d8c15cba13..b47abfc3a3aa 100644 --- a/builtin/providers/aws/autoscaling_tags_test.go +++ b/builtin/providers/aws/autoscaling_tags_test.go @@ -20,24 +20,28 @@ func TestDiffAutoscalingTags(t *testing.T) { { Old: map[string]interface{}{ "Name": map[string]interface{}{ + "key": "Name", "value": "bar", "propagate_at_launch": true, }, }, New: map[string]interface{}{ "DifferentTag": map[string]interface{}{ + "key": "DifferentTag", "value": "baz", "propagate_at_launch": true, }, }, Create: map[string]interface{}{ "DifferentTag": map[string]interface{}{ + "key": "DifferentTag", "value": "baz", "propagate_at_launch": true, }, }, Remove: map[string]interface{}{ "Name": map[string]interface{}{ + "key": "Name", "value": "bar", "propagate_at_launch": true, }, @@ -48,24 +52,28 @@ func TestDiffAutoscalingTags(t *testing.T) { { Old: map[string]interface{}{ "Name": map[string]interface{}{ + "key": "Name", "value": "bar", "propagate_at_launch": true, }, }, New: map[string]interface{}{ "Name": map[string]interface{}{ + "key": "Name", "value": "baz", "propagate_at_launch": false, }, }, Create: map[string]interface{}{ "Name": map[string]interface{}{ + "key": "Name", "value": "baz", "propagate_at_launch": false, }, }, Remove: map[string]interface{}{ "Name": map[string]interface{}{ + "key": "Name", "value": "bar", "propagate_at_launch": true, }, diff --git a/builtin/providers/aws/resource_aws_autoscaling_group.go b/builtin/providers/aws/resource_aws_autoscaling_group.go index ef21f2492c00..224872c0fd4b 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group.go @@ -244,7 +244,13 @@ func resourceAwsAutoscalingGroup() *schema.Resource { }, }, - "tag": autoscalingTagsSchema(), + "tag": autoscalingTagSchema(), + + "tags": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeMap}, + }, }, } } @@ -344,9 +350,15 @@ func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) createOpts.AvailabilityZones = expandStringList(v.(*schema.Set).List()) } + resourceID := d.Get("name").(string) if v, ok := d.GetOk("tag"); ok { createOpts.Tags = autoscalingTagsFromMap( - setToMapByKey(v.(*schema.Set), "key"), d.Get("name").(string)) + setToMapByKey(v.(*schema.Set), "key"), resourceID) + } + + if v, ok := d.GetOk("tags"); ok { + tags := autoscalingTagsFromList(v.([]interface{}), resourceID) + createOpts.Tags = append(createOpts.Tags, tags...) } if v, ok := d.GetOk("default_cooldown"); ok { @@ -457,7 +469,33 @@ func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) e d.Set("max_size", g.MaxSize) d.Set("placement_group", g.PlacementGroup) d.Set("name", g.AutoScalingGroupName) - d.Set("tag", autoscalingTagDescriptionsToSlice(g.Tags)) + + var tagList, tagsList []*autoscaling.TagDescription + if v, ok := d.GetOk("tag"); ok { + tags := setToMapByKey(v.(*schema.Set), "key") + for _, t := range g.Tags { + if _, ok := tags[*t.Key]; ok { + tagList = append(tagList, t) + } + } + d.Set("tag", autoscalingTagDescriptionsToSlice(tagList)) + } + + if v, ok := d.GetOk("tags"); ok { + tags := map[string]struct{}{} + for _, tag := range v.([]interface{}) { + attr := tag.(map[string]interface{}) + tags[attr["key"].(string)] = struct{}{} + } + + for _, t := range g.Tags { + if _, ok := tags[*t.Key]; ok { + tagsList = append(tagsList, t) + } + } + d.Set("tags", autoscalingTagDescriptionsToSlice(tagsList)) + } + d.Set("vpc_zone_identifier", strings.Split(*g.VPCZoneIdentifier, ",")) d.Set("protect_from_scale_in", g.NewInstancesProtectedFromScaleIn) @@ -549,10 +587,16 @@ func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{}) if err := setAutoscalingTags(conn, d); err != nil { return err - } else { + } + + if d.HasChange("tag") { d.SetPartial("tag") } + if d.HasChange("tags") { + d.SetPartial("tags") + } + log.Printf("[DEBUG] AutoScaling Group update configuration: %#v", opts) _, err := conn.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 ee17f88444ef..9aca71f428f3 100644 --- a/builtin/providers/aws/resource_aws_autoscaling_group_test.go +++ b/builtin/providers/aws/resource_aws_autoscaling_group_test.go @@ -74,8 +74,12 @@ func TestAccAWSAutoScalingGroup_basic(t *testing.T) { resource.TestCheckResourceAttr( "aws_autoscaling_group.bar", "protect_from_scale_in", "true"), testLaunchConfigurationName("aws_autoscaling_group.bar", &lc), - testAccCheckAutoscalingTags(&group.Tags, "Bar", map[string]interface{}{ - "value": "bar-foo", + testAccCheckAutoscalingTags(&group.Tags, "FromTagChanged", map[string]interface{}{ + "value": "value1changed", + "propagate_at_launch": true, + }), + testAccCheckAutoscalingTags(&group.Tags, "FromTags1", map[string]interface{}{ + "value": "value2", "propagate_at_launch": true, }), ), @@ -185,8 +189,16 @@ func TestAccAWSAutoScalingGroup_tags(t *testing.T) { Config: testAccAWSAutoScalingGroupConfig(randName), Check: resource.ComposeTestCheckFunc( testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), - testAccCheckAutoscalingTags(&group.Tags, "Foo", map[string]interface{}{ - "value": "foo-bar", + testAccCheckAutoscalingTags(&group.Tags, "FromTag", map[string]interface{}{ + "value": "value1", + "propagate_at_launch": true, + }), + testAccCheckAutoscalingTags(&group.Tags, "FromTags1", map[string]interface{}{ + "value": "value2", + "propagate_at_launch": true, + }), + testAccCheckAutoscalingTags(&group.Tags, "FromTags2", map[string]interface{}{ + "value": "value3", "propagate_at_launch": true, }), ), @@ -197,8 +209,12 @@ func TestAccAWSAutoScalingGroup_tags(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckAWSAutoScalingGroupExists("aws_autoscaling_group.bar", &group), testAccCheckAutoscalingTagNotExists(&group.Tags, "Foo"), - testAccCheckAutoscalingTags(&group.Tags, "Bar", map[string]interface{}{ - "value": "bar-foo", + testAccCheckAutoscalingTags(&group.Tags, "FromTagChanged", map[string]interface{}{ + "value": "value1changed", + "propagate_at_launch": true, + }), + testAccCheckAutoscalingTags(&group.Tags, "FromTags1", map[string]interface{}{ + "value": "value2", "propagate_at_launch": true, }), ), @@ -851,10 +867,23 @@ resource "aws_autoscaling_group" "bar" { launch_configuration = "${aws_launch_configuration.foobar.name}" tag { - key = "Foo" - value = "foo-bar" + key = "FromTag" + value = "value1" propagate_at_launch = true } + + tags = [ + { + key = "FromTags1" + value = "value2" + propagate_at_launch = true + }, + { + key = "FromTags2" + value = "value3" + propagate_at_launch = true + }, + ] } `, name, name) } @@ -886,10 +915,18 @@ resource "aws_autoscaling_group" "bar" { launch_configuration = "${aws_launch_configuration.new.name}" tag { - key = "Bar" - value = "bar-foo" + key = "FromTagChanged" + value = "value1changed" propagate_at_launch = true } + + tags = [ + { + key = "FromTags1" + value = "value2" + propagate_at_launch = true + }, + ] } `, name) } 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 07e21766d8d6..3ccbf9f1c542 100644 --- a/website/source/docs/providers/aws/r/autoscaling_group.html.markdown +++ b/website/source/docs/providers/aws/r/autoscaling_group.html.markdown @@ -52,11 +52,23 @@ EOF propagate_at_launch = true } - tag { - key = "lorem" - value = "ipsum" - propagate_at_launch = false - } + tags = [ + { + key = "explicit1" + value = "value1" + propagate_at_launch = true + }, + { + key = "explicit2" + value = "value2" + propagate_at_launch = true + }, + ] + + tags = ["${list( + map("key", "interpolation1", "value", "value3", "propagate_at_launch", true), + map("key", "interpolation2", "value", "value4", "propagate_at_launch", true) + )}"] } ``` @@ -100,6 +112,7 @@ Application Load Balancing * `suspended_processes` - (Optional) A list of processes to suspend for the AutoScaling Group. The allowed values are `Launch`, `Terminate`, `HealthCheck`, `ReplaceUnhealthy`, `AZRebalance`, `AlarmNotification`, `ScheduledActions`, `AddToLoadBalancer`. Note that if you suspend either the `Launch` or `Terminate` process types, it can prevent your autoscaling group from functioning properly. * `tag` (Optional) A list of tag blocks. Tags documented below. +* `tags` (Optional) A list of tag blocks (maps). Tags documented below. * `placement_group` (Optional) The name of the placement group into which you'll launch your instances, if any. * `metrics_granularity` - (Optional) The granularity to associate with the metrics to collect. The only valid value is `1Minute`. Default is `1Minute`. * `enabled_metrics` - (Optional) A list of metrics to collect. The allowed values are `GroupMinSize`, `GroupMaxSize`, `GroupDesiredCapacity`, `GroupInServiceInstances`, `GroupPendingInstances`, `GroupStandbyInstances`, `GroupTerminatingInstances`, `GroupTotalInstances`. @@ -128,6 +141,9 @@ Tags support the following: * `propagate_at_launch` - (Required) Enables propagation of the tag to Amazon EC2 instances launched via this ASG +Note that in addition to the `tag` field the `tags` field accepts a list of maps containing the above field names as keys and their respective values. +This allows the construction of custom tags using interpolation. + ## Attributes Reference The following attributes are exported: