Skip to content

Commit

Permalink
providers/aws: add tags for resource_aws_autoscaling_group
Browse files Browse the repository at this point in the history
The existing "tag" field on autoscaling groups is very limited in that it
cannot be used in conjunction with interpolation preventing from adding
dynamic tag entries.

Other AWS resources don't have this restriction on tags because they work
directly on the map type.

AWS autoscaling groups on the other hand have an additional field
"propagate_at_launch" which is not usable with a pure map type.

This fixes it by introducing an additional field called "tags" which
allows specifying a list of maps. This preserves the possibility to
declare tags as with the "tag" field but additionally allows to
construct lists of maps using interpolation syntax.
  • Loading branch information
Sergiusz Urbaniak committed Apr 26, 2017
1 parent c5a87af commit 82bda74
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 51 deletions.
135 changes: 105 additions & 30 deletions builtin/providers/aws/autoscaling_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import (
"fmt"
"log"
"regexp"
"strconv"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/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 {
// autoscalingTagSchema returns the schema to use for the tag element.
func autoscalingTagSchema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Expand All @@ -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)))
Expand All @@ -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
}
}

Expand All @@ -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,
}
Expand All @@ -115,31 +137,83 @@ 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, ok := tag.(map[string]interface{})
if !ok {
continue
}

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 {
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"),
for _, v := range m {
attr, ok := v.(map[string]interface{})
if !ok {
continue
}
if !tagIgnoredAutoscaling(t) {

t := autoscalingTagFromMap(attr, resourceID)
if t != nil {
result = append(result, t)
}
}

return result
}

func autoscalingTagFromMap(attr map[string]interface{}, resourceID string) *autoscaling.Tag {
if _, ok := attr["key"]; !ok {
return nil
}

if _, ok := attr["value"]; !ok {
return nil
}

if _, ok := attr["propagate_at_launch"]; !ok {
return nil
}

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,
}
Expand All @@ -154,6 +228,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,
}
Expand Down
8 changes: 8 additions & 0 deletions builtin/providers/aws/autoscaling_tags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -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,
},
Expand Down
61 changes: 57 additions & 4 deletions builtin/providers/aws/resource_aws_autoscaling_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
},
},
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -457,7 +469,42 @@ 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, ok := tag.(map[string]interface{})
if !ok {
continue
}

key, ok := attr["key"].(string)
if !ok {
continue
}

tags[key] = 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)

Expand Down Expand Up @@ -549,10 +596,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 {
Expand Down
Loading

0 comments on commit 82bda74

Please sign in to comment.