Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add instance tag support #173

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions builtin/providers/aws/resource_aws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package aws

import (
"log"
"sort"

"github.com/hashicorp/terraform/flatmap"
"github.com/mitchellh/goamz/ec2"
)

func resource_aws_build_tags(attributes map[string]string, prefix string) []ec2.Tag {
tags := make([]ec2.Tag, 0)

if rawTagList := flatmap.Expand(attributes, prefix); rawTagList != nil {
if tagList, ok := rawTagList.([]interface{}); ok {
for _, rawTag := range tagList {
tag, ok := rawTag.(map[string]interface{})
if !ok {
continue
}

tagKeyRaw, ok := tag["key"]
if !ok {
continue
}

tagValueRaw, ok := tag["value"]
if !ok {
continue
}

tagKey, ok := tagKeyRaw.(string)
if !ok {
continue
}

tagValue, ok := tagValueRaw.(string)
if !ok {
continue
}

tags = append(tags, ec2.Tag{
Key: tagKey,
Value: tagValue,
})
}
}
}

sort.Stable(sortableTags(tags))

return tags
}

func resource_aws_sync_tags(ec2conn *ec2.EC2, resourceId string, oldTags, newTags []ec2.Tag) error {
toDelete := make([]ec2.Tag, 0)
toModify := make([]ec2.Tag, 0)

for i := 0; i < len(oldTags); i++ {
found := false

for j := 0; j < len(newTags); j++ {
if oldTags[i].Key == newTags[j].Key {
found = true

if oldTags[i].Value != newTags[j].Value {
toModify = append(toModify, ec2.Tag{
Key: oldTags[i].Key,
Value: newTags[j].Value,
})
}

break
}
}

if !found {
toDelete = append(toDelete, ec2.Tag{
Key: oldTags[i].Key,
})
}
}

for i := 0; i < len(newTags); i++ {
found := false

for j := 0; j < len(oldTags); j++ {
if newTags[i].Key == oldTags[j].Key {
found = true

break
}
}

if !found {
toModify = append(toModify, ec2.Tag{
Key: newTags[i].Key,
Value: newTags[i].Value,
})
}
}

log.Printf("[DEBUG] deleting tags: %#v", toDelete)
log.Printf("[DEBUG] modifying tags: %#v", toModify)

if len(toDelete) > 0 {
if _, err := ec2conn.DeleteTags([]string{resourceId}, toDelete); err != nil {
return err
}
}

if len(toModify) > 0 {
if _, err := ec2conn.CreateTags([]string{resourceId}, toModify); err != nil {
return err
}
}

return nil
}

type sortableTags []ec2.Tag

func (s sortableTags) Len() int { return len(s) }
func (s sortableTags) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s sortableTags) Less(i, j int) bool { return s[i].Key < s[j].Key }
49 changes: 46 additions & 3 deletions builtin/providers/aws/resource_aws_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/hex"
"fmt"
"log"
"sort"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -69,6 +70,8 @@ func resource_aws_instance_create(
}
}

tags := resource_aws_build_tags(rs.Attributes, "tag")

// Create the instance
log.Printf("[DEBUG] Run configuration: %#v", runOpts)
runResp, err := ec2conn.RunInstances(runOpts)
Expand All @@ -82,6 +85,12 @@ func resource_aws_instance_create(
// Store the resulting ID so we can look this up later
rs.ID = instance.InstanceId

if len(tags) > 0 {
if _, err := ec2conn.CreateTags([]string{rs.ID}, tags); err != nil {
return nil, err
}
}

// Wait for the instance to become running so we can get some attributes
// that aren't available until later.
log.Printf(
Expand Down Expand Up @@ -112,7 +121,7 @@ func resource_aws_instance_create(
rs.ConnInfo["host"] = instance.PublicIpAddress

// Set our attributes
rs, err = resource_aws_instance_update_state(rs, instance)
rs, err = resource_aws_instance_update_state(rs, instance, tags)
if err != nil {
return rs, err
}
Expand Down Expand Up @@ -150,6 +159,13 @@ func resource_aws_instance_update(
// persist the change...
}

oldTags := resource_aws_build_tags(s.Attributes, "tag")
newTags := resource_aws_build_tags(rs.Attributes, "tag")

if err := resource_aws_sync_tags(ec2conn, s.ID, oldTags, newTags); err != nil {
return nil, err
}

return rs, nil
}

Expand Down Expand Up @@ -201,6 +217,7 @@ func resource_aws_instance_diff(
"security_groups": diff.AttrTypeCreate,
"subnet_id": diff.AttrTypeCreate,
"source_dest_check": diff.AttrTypeUpdate,
"tag": diff.AttrTypeUpdate,
"user_data": diff.AttrTypeCreate,
"associate_public_ip_address": diff.AttrTypeCreate,
},
Expand Down Expand Up @@ -257,12 +274,27 @@ func resource_aws_instance_refresh(
return nil, nil
}

return resource_aws_instance_update_state(s, instance)
filter := ec2.NewFilter()
filter.Add("resource-id", s.ID)
tagsResp, err := ec2conn.Tags(filter)
if err != nil {
return nil, err
}

tags := make([]ec2.Tag, len(tagsResp.Tags))
for i, v := range tagsResp.Tags {
tags[i] = v.Tag
}

sort.Stable(sortableTags(tags))

return resource_aws_instance_update_state(s, instance, tags)
}

func resource_aws_instance_update_state(
s *terraform.ResourceState,
instance *ec2.Instance) (*terraform.ResourceState, error) {
instance *ec2.Instance,
tags []ec2.Tag) (*terraform.ResourceState, error) {
s.Attributes["availability_zone"] = instance.AvailZone
s.Attributes["key_name"] = instance.KeyName
s.Attributes["public_dns"] = instance.DNSName
Expand Down Expand Up @@ -313,6 +345,17 @@ func resource_aws_instance_update_state(
)
}

toFlatten := make([]map[string]string, 0)
for _, tag := range tags {
toFlatten = append(toFlatten, map[string]string{
"key": tag.Key,
"value": tag.Value,
})
}
flatmap.Map(s.Attributes).Merge(flatmap.Flatten(map[string]interface{}{
"tag": toFlatten,
}))

return s, nil
}

Expand Down
11 changes: 11 additions & 0 deletions website/source/docs/providers/aws/r/instance.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ and deleted. Instances also support [provisioning](/docs/provisioners/index.html
resource "aws_instance" "web" {
ami = "ami-1234"
instance_type = "m1.small"

# optional tags (carefully note the ordering)
tag {
key = Customer
value = "widgets inc."
}
tag {
key = Name
value = "web server"
}
}
```

Expand All @@ -35,6 +45,7 @@ The following arguments are supported:
* `source_dest_check` - (Optional) Controls if traffic is routed to the instance when
the destination address does not match the instance. Used for NAT or VPNs. Defaults false.
* `user_data` - (Optional) The user data to provide when launching the instance.
* `tag` - (Optional) Tags for the instance. NOTE: tags should be specified in alphabetical order of their keys, as keys are returned by Amazon in an arbitrary order and terraform sorts them in order to have a canonical representation.

## Attributes Reference

Expand Down