From 3bf1c52c80525a6a6410a68d00828ff1364ab118 Mon Sep 17 00:00:00 2001 From: kterada0509 Date: Wed, 17 Jul 2019 19:23:00 +0900 Subject: [PATCH] Add support resource tags for aws_media_store_container resource --- aws/resource_aws_media_store_container.go | 36 ++++- ...resource_aws_media_store_container_test.go | 59 ++++++++ aws/tagsMediaStore.go | 135 ++++++++++++++++++ aws/tagsMediaStore_test.go | 112 +++++++++++++++ .../r/media_store_container.html.markdown | 1 + 5 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 aws/tagsMediaStore.go create mode 100644 aws/tagsMediaStore_test.go diff --git a/aws/resource_aws_media_store_container.go b/aws/resource_aws_media_store_container.go index ce76b6ad93e..2ca5f58ea56 100644 --- a/aws/resource_aws_media_store_container.go +++ b/aws/resource_aws_media_store_container.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "log" "regexp" "time" @@ -16,6 +17,7 @@ func resourceAwsMediaStoreContainer() *schema.Resource { return &schema.Resource{ Create: resourceAwsMediaStoreContainerCreate, Read: resourceAwsMediaStoreContainerRead, + Update: resourceAwsMediaStoreContainerUpdate, Delete: resourceAwsMediaStoreContainerDelete, Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, @@ -35,6 +37,7 @@ func resourceAwsMediaStoreContainer() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "tags": tagsSchema(), }, } } @@ -44,6 +47,7 @@ func resourceAwsMediaStoreContainerCreate(d *schema.ResourceData, meta interface input := &mediastore.CreateContainerInput{ ContainerName: aws.String(d.Get("name").(string)), + Tags: tagsFromMapMediaStore(d.Get("tags").(map[string]interface{})), } _, err := conn.CreateContainer(input) @@ -75,15 +79,45 @@ func resourceAwsMediaStoreContainerRead(d *schema.ResourceData, meta interface{} ContainerName: aws.String(d.Id()), } resp, err := conn.DescribeContainer(input) + if isAWSErr(err, mediastore.ErrCodeContainerNotFoundException, "") { + log.Printf("[WARN] No Container found: %s, removing from state", d.Id()) + d.SetId("") + return nil + } if err != nil { - return err + return fmt.Errorf("Error describing media store container %s: %s", d.Id(), err) } d.Set("arn", resp.Container.ARN) d.Set("name", resp.Container.Name) d.Set("endpoint", resp.Container.Endpoint) + + if err := saveTagsMediaStore(conn, d, aws.StringValue(resp.Container.ARN)); err != nil { + if isAWSErr(err, mediastore.ErrCodeContainerNotFoundException, "") { + log.Printf("[WARN] No Container found: %s, removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error setting tags for %s: %s", d.Id(), err) + } + return nil } +func resourceAwsMediaStoreContainerUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).mediastoreconn + + if err := setTagsMediaStore(conn, d, d.Get("arn").(string)); err != nil { + if isAWSErr(err, mediastore.ErrCodeContainerNotFoundException, "") { + log.Printf("[WARN] No Container found: %s, removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error updating tags for %s: %s", d.Id(), err) + } + + return resourceAwsMediaStoreContainerRead(d, meta) +} + func resourceAwsMediaStoreContainerDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).mediastoreconn diff --git a/aws/resource_aws_media_store_container_test.go b/aws/resource_aws_media_store_container_test.go index ab609c14d1c..d332db1136a 100644 --- a/aws/resource_aws_media_store_container_test.go +++ b/aws/resource_aws_media_store_container_test.go @@ -27,6 +27,51 @@ func TestAccAWSMediaStoreContainer_basic(t *testing.T) { }) } +func TestAccAWSMediaStoreContainer_tags(t *testing.T) { + rName := acctest.RandString(5) + resourceName := "aws_media_store_container.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSMediaStore(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsMediaStoreContainerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccMediaStoreContainerConfigWithTags(rName, "foo", "bar", "fizz", "buzz"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMediaStoreContainerExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", fmt.Sprintf("tf_mediastore_%s", rName)), + resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar"), + resource.TestCheckResourceAttr(resourceName, "tags.fizz", "buzz"), + ), + }, + { + Config: testAccMediaStoreContainerConfigWithTags(rName, "foo", "bar2", "fizz2", "buzz2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMediaStoreContainerExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), + resource.TestCheckResourceAttr(resourceName, "tags.Name", fmt.Sprintf("tf_mediastore_%s", rName)), + resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar2"), + resource.TestCheckResourceAttr(resourceName, "tags.fizz2", "buzz2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccMediaStoreContainerConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsMediaStoreContainerExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + func TestAccAWSMediaStoreContainer_import(t *testing.T) { resourceName := "aws_media_store_container.test" @@ -116,3 +161,17 @@ resource "aws_media_store_container" "test" { } `, rName) } + +func testAccMediaStoreContainerConfigWithTags(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_media_store_container" "test" { + name = "tf_mediastore_%[1]s" + + tags = { + Name = "tf_mediastore_%[1]s" + %[2]s = %[3]q + %[4]s = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/aws/tagsMediaStore.go b/aws/tagsMediaStore.go new file mode 100644 index 00000000000..2683a0afcbb --- /dev/null +++ b/aws/tagsMediaStore.go @@ -0,0 +1,135 @@ +package aws + +import ( + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/mediastore" + "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 setTagsMediaStore(conn *mediastore.MediaStore, d *schema.ResourceData, arn string) error { + if d.HasChange("tags") { + oraw, nraw := d.GetChange("tags") + o := oraw.(map[string]interface{}) + n := nraw.(map[string]interface{}) + create, remove := diffTagsMediaStore(tagsFromMapMediaStore(o), tagsFromMapMediaStore(n)) + + // Set tags + if len(remove) > 0 { + log.Printf("[DEBUG] Removing tags: %s", remove) + k := make([]*string, len(remove)) + for i, t := range remove { + k[i] = t.Key + } + + _, err := conn.UntagResource(&mediastore.UntagResourceInput{ + Resource: aws.String(arn), + TagKeys: k, + }) + if err != nil { + return err + } + } + if len(create) > 0 { + log.Printf("[DEBUG] Creating tags: %s", create) + _, err := conn.TagResource(&mediastore.TagResourceInput{ + Resource: aws.String(arn), + Tags: create, + }) + 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 diffTagsMediaStore(oldTags, newTags []*mediastore.Tag) ([]*mediastore.Tag, []*mediastore.Tag) { + // First, we're creating everything we have + create := make(map[string]interface{}) + for _, t := range newTags { + create[aws.StringValue(t.Key)] = aws.StringValue(t.Value) + } + + // Build the list of what to remove + var remove []*mediastore.Tag + for _, t := range oldTags { + old, ok := create[aws.StringValue(t.Key)] + if !ok || old != aws.StringValue(t.Value) { + // Delete it! + remove = append(remove, t) + } else if ok { + delete(create, aws.StringValue(t.Key)) + } + } + + return tagsFromMapMediaStore(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMapMediaStore(m map[string]interface{}) []*mediastore.Tag { + result := make([]*mediastore.Tag, 0, len(m)) + for k, v := range m { + t := &mediastore.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + } + if !tagIgnoredMediaStore(t) { + result = append(result, t) + } + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMapMediaStore(ts []*mediastore.Tag) map[string]string { + result := make(map[string]string) + for _, t := range ts { + if !tagIgnoredMediaStore(t) { + result[aws.StringValue(t.Key)] = aws.StringValue(t.Value) + } + } + + return result +} + +// compare a tag against a list of strings and checks if it should +// be ignored or not +func tagIgnoredMediaStore(t *mediastore.Tag) bool { + filter := []string{"^aws:"} + for _, v := range filter { + log.Printf("[DEBUG] Matching %v with %v\n", v, aws.StringValue(t.Key)) + r, _ := regexp.MatchString(v, aws.StringValue(t.Key)) + if r { + log.Printf("[DEBUG] Found AWS specific tag %s (val: %s), ignoring.\n", aws.StringValue(t.Key), aws.StringValue(t.Value)) + return true + } + } + return false +} + +func saveTagsMediaStore(conn *mediastore.MediaStore, d *schema.ResourceData, arn string) error { + resp, err := conn.ListTagsForResource(&mediastore.ListTagsForResourceInput{ + Resource: aws.String(arn), + }) + + if err != nil { + return err + } + + var dt []*mediastore.Tag + if len(resp.Tags) > 0 { + dt = resp.Tags + } + + return d.Set("tags", tagsToMapMediaStore(dt)) +} diff --git a/aws/tagsMediaStore_test.go b/aws/tagsMediaStore_test.go new file mode 100644 index 00000000000..29d0acba31e --- /dev/null +++ b/aws/tagsMediaStore_test.go @@ -0,0 +1,112 @@ +package aws + +import ( + "reflect" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/mediastore" +) + +// go test -v -run="TestDiffMediaStoreTags" +func TestDiffMediaStoreTags(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", + }, + }, + + // Overlap + { + Old: map[string]interface{}{ + "foo": "bar", + "hello": "world", + }, + New: map[string]interface{}{ + "foo": "baz", + "hello": "world", + }, + Create: map[string]string{ + "foo": "baz", + }, + Remove: map[string]string{ + "foo": "bar", + }, + }, + + // Remove + { + Old: map[string]interface{}{ + "foo": "bar", + "bar": "baz", + }, + New: map[string]interface{}{ + "foo": "bar", + }, + Create: map[string]string{}, + Remove: map[string]string{ + "bar": "baz", + }, + }, + } + + for i, tc := range cases { + c, r := diffTagsMediaStore(tagsFromMapMediaStore(tc.Old), tagsFromMapMediaStore(tc.New)) + cm := tagsToMapMediaStore(c) + rm := tagsToMapMediaStore(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) + } + } +} + +// go test -v -run="TestIgnoringTagsMediaStore" +func TestIgnoringTagsMediaStore(t *testing.T) { + var ignoredTags []*mediastore.Tag + ignoredTags = append(ignoredTags, &mediastore.Tag{ + Key: aws.String("aws:cloudformation:logical-id"), + Value: aws.String("foo"), + }) + ignoredTags = append(ignoredTags, &mediastore.Tag{ + Key: aws.String("aws:foo:bar"), + Value: aws.String("baz"), + }) + for _, tag := range ignoredTags { + if !tagIgnoredMediaStore(tag) { + t.Fatalf("Tag %v with value %v not ignored, but should be!", *tag.Key, *tag.Value) + } + } +} diff --git a/website/docs/r/media_store_container.html.markdown b/website/docs/r/media_store_container.html.markdown index ff16d4580ac..65bdc615610 100644 --- a/website/docs/r/media_store_container.html.markdown +++ b/website/docs/r/media_store_container.html.markdown @@ -23,6 +23,7 @@ resource "aws_media_store_container" "example" { The following arguments are supported: * `name` - (Required) The name of the container. Must contain alphanumeric characters or underscores. +* `tags` - (Optional) A mapping of tags to assign to the resource. ## Attributes Reference