diff --git a/builtin/providers/aws/resource_aws_vpc.go b/builtin/providers/aws/resource_aws_vpc.go index e08995752..cde7b9065 100644 --- a/builtin/providers/aws/resource_aws_vpc.go +++ b/builtin/providers/aws/resource_aws_vpc.go @@ -5,8 +5,8 @@ import ( "log" "time" - "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" "github.com/mitchellh/goamz/ec2" ) @@ -35,6 +35,8 @@ func resourceAwsVpc() *schema.Resource { Optional: true, Computed: true, }, + + "tags": tagsSchema(), }, } } @@ -120,10 +122,15 @@ func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error { d.SetPartial("enable_dns_support") } + if err := setTags(ec2conn, d); err != nil { + return err + } else { + d.SetPartial("tags") + } + return nil } - func resourceAwsVpcDelete(d *schema.ResourceData, meta interface{}) error { p := meta.(*ResourceProvider) ec2conn := p.ec2conn @@ -158,6 +165,9 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error { vpc := vpcRaw.(*ec2.VPC) d.Set("cidr_block", vpc.CidrBlock) + // Tags + d.Set("tags", tagsToMap(vpc.Tags)) + // Attributes resp, err := ec2conn.VpcAttribute(d.Id(), "enableDnsSupport") if err != nil { diff --git a/builtin/providers/aws/resource_aws_vpc_test.go b/builtin/providers/aws/resource_aws_vpc_test.go index f19686ada..83528640b 100644 --- a/builtin/providers/aws/resource_aws_vpc_test.go +++ b/builtin/providers/aws/resource_aws_vpc_test.go @@ -30,6 +30,28 @@ func TestAccVpc_basic(t *testing.T) { }) } +func TestAccVpc_tags(t *testing.T) { + var vpc ec2.VPC + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVpcDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccVpcConfigTags, + Check: resource.ComposeTestCheckFunc( + testAccCheckVpcExists("aws_vpc.foo", &vpc), + testAccCheckVpcCidr(&vpc, "10.1.0.0/16"), + resource.TestCheckResourceAttr( + "aws_vpc.foo", "cidr_block", "10.1.0.0/16"), + testAccCheckTags(&vpc.Tags, "foo", "bar"), + ), + }, + }, + }) +} + func TestAccVpcUpdate(t *testing.T) { var vpc ec2.VPC @@ -138,3 +160,13 @@ resource "aws_vpc" "foo" { enable_dns_hostnames = true } ` + +const testAccVpcConfigTags = ` +resource "aws_vpc" "foo" { + cidr_block = "10.1.0.0/16" + + tags { + foo = "bar" + } +} +` diff --git a/builtin/providers/aws/tags.go b/builtin/providers/aws/tags.go new file mode 100644 index 000000000..c0dd4e04d --- /dev/null +++ b/builtin/providers/aws/tags.go @@ -0,0 +1,89 @@ +package aws + +import ( + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/mitchellh/goamz/ec2" +) + +// tagsSchema returns the schema to use for tags. +func tagsSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + Computed: true, + } +} + +// setTags is a helper to set the tags for a resource. It expects the +// tags field to be named "tags" +func setTags(conn *ec2.EC2, d *schema.ResourceData) error { + if d.HasChange("tags") { + oraw, nraw := d.GetChange("tags") + o := oraw.(map[string]interface{}) + n := nraw.(map[string]interface{}) + create, remove := diffTags(tagsFromMap(o), tagsFromMap(n)) + + // Set tags + if len(remove) > 0 { + log.Printf("[DEBUG] Removing tags: %#v", remove) + if _, err := conn.DeleteTags([]string{d.Id()}, remove); err != nil { + return err + } + } + if len(create) > 0 { + log.Printf("[DEBUG] Creating tags: %#v", create) + if _, err := conn.CreateTags([]string{d.Id()}, create); err != nil { + return err + } + } + } + + return nil +} + +// diffTags takes our tags locally and the ones remotely and returns +// the set of tags that must be created, and the set of tags that must +// be destroyed. +func diffTags(oldTags, newTags []ec2.Tag) ([]ec2.Tag, []ec2.Tag) { + // First, we're creating everything we have + create := make(map[string]interface{}) + for _, t := range newTags { + create[t.Key] = t.Value + } + + // Build the list of what to remove + var remove []ec2.Tag + for _, t := range oldTags { + if _, ok := create[t.Key]; !ok { + // Delete it! + remove = append(remove, t) + } + } + + return tagsFromMap(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMap(m map[string]interface{}) []ec2.Tag { + result := make([]ec2.Tag, 0, len(m)) + for k, v := range m { + result = append(result, ec2.Tag{ + Key: k, + Value: v.(string), + }) + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMap(ts []ec2.Tag) map[string]string { + result := make(map[string]string) + for _, t := range ts { + result[t.Key] = t.Value + } + + return result +} diff --git a/builtin/providers/aws/tags_test.go b/builtin/providers/aws/tags_test.go new file mode 100644 index 000000000..55f842dbe --- /dev/null +++ b/builtin/providers/aws/tags_test.go @@ -0,0 +1,26 @@ +package aws + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/goamz/ec2" +) + +// testAccCheckTags can be used to check the tags on a resource. +func testAccCheckTags( + ts *[]ec2.Tag, key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + m := tagsToMap(*ts) + v, ok := m[key] + if !ok { + return fmt.Errorf("Missing tag: %s", key) + } + if v != value { + return fmt.Errorf("%s: bad value: %s", key, v) + } + + return nil + } +} diff --git a/helper/schema/resource_data.go b/helper/schema/resource_data.go index fbef8c88b..ee2e36339 100644 --- a/helper/schema/resource_data.go +++ b/helper/schema/resource_data.go @@ -381,6 +381,14 @@ func (d *ResourceData) getMap( } } + resultSet = true + case []map[string]interface{}: + for _, innerRaw := range m { + for k, v := range innerRaw { + result[k] = v + } + } + resultSet = true case map[string]interface{}: result = m