diff --git a/builtin/providers/aws/resource_aws_route53_zone.go b/builtin/providers/aws/resource_aws_route53_zone.go index 6d9914b7f088..b60c91a799d2 100644 --- a/builtin/providers/aws/resource_aws_route53_zone.go +++ b/builtin/providers/aws/resource_aws_route53_zone.go @@ -16,6 +16,7 @@ func resourceAwsRoute53Zone() *schema.Resource { return &schema.Resource{ Create: resourceAwsRoute53ZoneCreate, Read: resourceAwsRoute53ZoneRead, + Update: resourceAwsRoute53ZoneUpdate, Delete: resourceAwsRoute53ZoneDelete, Schema: map[string]*schema.Schema{ @@ -29,6 +30,8 @@ func resourceAwsRoute53Zone() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "tags": tagsSchema(), }, } } @@ -72,7 +75,7 @@ func resourceAwsRoute53ZoneCreate(d *schema.ResourceData, meta interface{}) erro if err != nil { return err } - return nil + return resourceAwsRoute53ZoneUpdate(d, meta) } func resourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error { @@ -87,9 +90,41 @@ func resourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error return err } + // get tags + req := &route53.ListTagsForResourceRequest{ + ResourceID: aws.String(d.Id()), + ResourceType: aws.String("hostedzone"), + } + + resp, err := r53.ListTagsForResource(req) + if err != nil { + return err + } + + var tags []route53.Tag + if resp.ResourceTagSet != nil { + tags = resp.ResourceTagSet.Tags + } + + if err := d.Set("tags", tagsToMapR53(tags)); err != nil { + return err + } + return nil } +func resourceAwsRoute53ZoneUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).r53conn + + if err := setTagsR53(conn, d); err != nil { + return err + } else { + d.SetPartial("tags") + } + + return resourceAwsRoute53ZoneRead(d, meta) +} + func resourceAwsRoute53ZoneDelete(d *schema.ResourceData, meta interface{}) error { r53 := meta.(*AWSClient).r53conn diff --git a/builtin/providers/aws/resource_aws_route53_zone_test.go b/builtin/providers/aws/resource_aws_route53_zone_test.go index fa78634cf79b..0669f88b1cde 100644 --- a/builtin/providers/aws/resource_aws_route53_zone_test.go +++ b/builtin/providers/aws/resource_aws_route53_zone_test.go @@ -63,6 +63,9 @@ func TestCleanChangeID(t *testing.T) { } func TestAccRoute53Zone(t *testing.T) { + var zone route53.HostedZone + var td route53.ResourceTagSet + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -71,7 +74,9 @@ func TestAccRoute53Zone(t *testing.T) { resource.TestStep{ Config: testAccRoute53ZoneConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckRoute53ZoneExists("aws_route53_zone.main"), + testAccCheckRoute53ZoneExists("aws_route53_zone.main", &zone), + testAccLoadTagsR53(&zone, &td), + testAccCheckTagsR53(&td.Tags, "foo", "bar"), ), }, }, @@ -93,7 +98,7 @@ func testAccCheckRoute53ZoneDestroy(s *terraform.State) error { return nil } -func testAccCheckRoute53ZoneExists(n string) resource.TestCheckFunc { +func testAccCheckRoute53ZoneExists(n string, zone *route53.HostedZone) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -105,10 +110,34 @@ func testAccCheckRoute53ZoneExists(n string) resource.TestCheckFunc { } conn := testAccProvider.Meta().(*AWSClient).r53conn - _, err := conn.GetHostedZone(&route53.GetHostedZoneRequest{ID: aws.String(rs.Primary.ID)}) + resp, err := conn.GetHostedZone(&route53.GetHostedZoneRequest{ID: aws.String(rs.Primary.ID)}) if err != nil { return fmt.Errorf("Hosted zone err: %v", err) } + *zone = *resp.HostedZone + return nil + } +} + +func testAccLoadTagsR53(zone *route53.HostedZone, td *route53.ResourceTagSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).r53conn + + zone := cleanZoneID(*zone.ID) + req := &route53.ListTagsForResourceRequest{ + ResourceID: aws.String(zone), + ResourceType: aws.String("hostedzone"), + } + + resp, err := conn.ListTagsForResource(req) + if err != nil { + return err + } + + if resp.ResourceTagSet != nil { + *td = *resp.ResourceTagSet + } + return nil } } @@ -116,5 +145,10 @@ func testAccCheckRoute53ZoneExists(n string) resource.TestCheckFunc { const testAccRoute53ZoneConfig = ` resource "aws_route53_zone" "main" { name = "hashicorp.com" + + tags { + foo = "bar" + Name = "tf-route53-tag-test" + } } ` diff --git a/builtin/providers/aws/tags_route53.go b/builtin/providers/aws/tags_route53.go new file mode 100644 index 000000000000..e5251d02a077 --- /dev/null +++ b/builtin/providers/aws/tags_route53.go @@ -0,0 +1,86 @@ +package aws + +import ( + "log" + + "github.com/hashicorp/aws-sdk-go/aws" + "github.com/hashicorp/aws-sdk-go/gen/route53" + "github.com/hashicorp/terraform/helper/schema" +) + +// setTags is a helper to set the tags for a resource. It expects the +// tags field to be named "tags" +func setTagsR53(conn *route53.Route53, 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 := diffTagsR53(tagsFromMapR53(o), tagsFromMapR53(n)) + + // Set tags + r := make([]string, len(remove)) + for i, t := range remove { + r[i] = *t.Key + } + log.Printf("[DEBUG] Changing tags: \n\tadding: %#v\n\tremoving:%#v", create, remove) + req := &route53.ChangeTagsForResourceRequest{ + AddTags: create, + RemoveTagKeys: r, + ResourceID: aws.String(d.Id()), + ResourceType: aws.String("hostedzone"), + } + + _, err := conn.ChangeTagsForResource(req) + if 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 diffTagsR53(oldTags, newTags []route53.Tag) ([]route53.Tag, []route53.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 []route53.Tag + for _, t := range oldTags { + old, ok := create[*t.Key] + if !ok || old != *t.Value { + // Delete it! + remove = append(remove, t) + } + } + + return tagsFromMapR53(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMapR53(m map[string]interface{}) []route53.Tag { + result := make([]route53.Tag, 0, len(m)) + for k, v := range m { + result = append(result, route53.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + }) + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMapR53(ts []route53.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_route53_test.go b/builtin/providers/aws/tags_route53_test.go new file mode 100644 index 000000000000..40a4154f3f46 --- /dev/null +++ b/builtin/providers/aws/tags_route53_test.go @@ -0,0 +1,85 @@ +package aws + +import ( + "fmt" + "reflect" + "testing" + + "github.com/hashicorp/aws-sdk-go/gen/route53" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestDiffTagsR53(t *testing.T) { + cases := []struct { + Old, New map[string]interface{} + Create, Remove map[string]string + }{ + // Basic add/remove + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "bar": "baz", + }, + Create: map[string]string{ + "bar": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + + // Modify + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "foo": "baz", + }, + Create: map[string]string{ + "foo": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + } + + for i, tc := range cases { + c, r := diffTagsR53(tagsFromMapR53(tc.Old), tagsFromMapR53(tc.New)) + cm := tagsToMapR53(c) + rm := tagsToMapR53(r) + if !reflect.DeepEqual(cm, tc.Create) { + t.Fatalf("%d: bad create: %#v", i, cm) + } + if !reflect.DeepEqual(rm, tc.Remove) { + t.Fatalf("%d: bad remove: %#v", i, rm) + } + } +} + +// testAccCheckTags can be used to check the tags on a resource. +func testAccCheckTagsR53( + ts *[]route53.Tag, key string, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + m := tagsToMapR53(*ts) + v, ok := m[key] + if value != "" && !ok { + return fmt.Errorf("Missing tag: %s", key) + } else if value == "" && ok { + return fmt.Errorf("Extra tag: %s", key) + } + if value == "" { + return nil + } + + if v != value { + return fmt.Errorf("%s: bad value: %s", key, v) + } + + return nil + } +}