From e2c55b7415f639bfb26dc6451c0344523734e8d5 Mon Sep 17 00:00:00 2001 From: kterada0509 Date: Wed, 15 May 2019 18:19:18 +0900 Subject: [PATCH] r/aws_kinesis_analytics_application: Add support for resource tags --- ...ource_aws_kinesis_analytics_application.go | 14 ++ ..._aws_kinesis_analytics_application_test.go | 85 +++++++++++ aws/tagsKinesisAnalytics.go | 135 ++++++++++++++++++ aws/tagsKinesisAnalytics_test.go | 109 ++++++++++++++ ...inesis_analytics_application.html.markdown | 1 + 5 files changed, 344 insertions(+) create mode 100644 aws/tagsKinesisAnalytics.go create mode 100644 aws/tagsKinesisAnalytics_test.go diff --git a/aws/resource_aws_kinesis_analytics_application.go b/aws/resource_aws_kinesis_analytics_application.go index 79eac8f8829..7d0882e5a3a 100644 --- a/aws/resource_aws_kinesis_analytics_application.go +++ b/aws/resource_aws_kinesis_analytics_application.go @@ -552,6 +552,7 @@ func resourceAwsKinesisAnalyticsApplication() *schema.Resource { }, }, }, + "tags": tagsSchema(), }, } } @@ -588,6 +589,10 @@ func resourceAwsKinesisAnalyticsApplicationCreate(d *schema.ResourceData, meta i createOpts.Outputs = outputs } + if v, ok := d.GetOk("tags"); ok { + createOpts.Tags = tagsFromMapKinesisAnalytics(v.(map[string]interface{})) + } + // Retry for IAM eventual consistency err := resource.Retry(1*time.Minute, func() *resource.RetryError { output, err := conn.CreateApplication(createOpts) @@ -658,6 +663,10 @@ func resourceAwsKinesisAnalyticsApplicationRead(d *schema.ResourceData, meta int return fmt.Errorf("error setting reference_data_sources: %s", err) } + if err := getTagsKinesisAnalytics(conn, d); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + return nil } @@ -783,6 +792,11 @@ func resourceAwsKinesisAnalyticsApplicationUpdate(d *schema.ResourceData, meta i version = version + 1 } } + + if err := setTagsKinesisAnalytics(conn, d); err != nil { + return fmt.Errorf("Error update resource tags for %s: %s", d.Id(), err) + } + } oldReferenceData, newReferenceData := d.GetChange("reference_data_sources") diff --git a/aws/resource_aws_kinesis_analytics_application_test.go b/aws/resource_aws_kinesis_analytics_application_test.go index 2f4b04cfc9a..fb3e31025b1 100644 --- a/aws/resource_aws_kinesis_analytics_application_test.go +++ b/aws/resource_aws_kinesis_analytics_application_test.go @@ -583,6 +583,62 @@ func TestAccAWSKinesisAnalyticsApplication_referenceDataSourceUpdate(t *testing. }) } +func TestAccAWSKinesisAnalyticsApplication_tags(t *testing.T) { + var application kinesisanalytics.ApplicationDetail + resName := "aws_kinesis_analytics_application.test" + rInt := acctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckKinesisAnalyticsApplicationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKinesisAnalyticsApplicationWithTags(rInt, "test1", "test2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKinesisAnalyticsApplicationExists(resName, &application), + resource.TestCheckResourceAttr(resName, "tags.%", "2"), + resource.TestCheckResourceAttr(resName, "tags.firstTag", "test1"), + resource.TestCheckResourceAttr(resName, "tags.secondTag", "test2"), + ), + }, + { + Config: testAccKinesisAnalyticsApplicationWithAddTags(rInt, "test1", "test2", "test3"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKinesisAnalyticsApplicationExists(resName, &application), + resource.TestCheckResourceAttr(resName, "tags.%", "3"), + resource.TestCheckResourceAttr(resName, "tags.firstTag", "test1"), + resource.TestCheckResourceAttr(resName, "tags.secondTag", "test2"), + resource.TestCheckResourceAttr(resName, "tags.thirdTag", "test3"), + ), + }, + { + Config: testAccKinesisAnalyticsApplicationWithTags(rInt, "test1", "test2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKinesisAnalyticsApplicationExists(resName, &application), + resource.TestCheckResourceAttr(resName, "tags.%", "2"), + resource.TestCheckResourceAttr(resName, "tags.firstTag", "test1"), + resource.TestCheckResourceAttr(resName, "tags.secondTag", "test2"), + ), + }, + { + Config: testAccKinesisAnalyticsApplicationWithTags(rInt, "test1", "update_test2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckKinesisAnalyticsApplicationExists(resName, &application), + resource.TestCheckResourceAttr(resName, "tags.%", "2"), + resource.TestCheckResourceAttr(resName, "tags.firstTag", "test1"), + resource.TestCheckResourceAttr(resName, "tags.secondTag", "update_test2"), + ), + }, + { + ResourceName: resName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckKinesisAnalyticsApplicationDestroy(s *terraform.State) error { for _, rs := range s.RootModule().Resources { if rs.Type != "aws_kinesis_analytics_application" { @@ -1111,3 +1167,32 @@ resource "aws_iam_role_policy_attachment" "test" { } `, rInt, rInt) } + +func testAccKinesisAnalyticsApplicationWithTags(rInt int, tag1, tag2 string) string { + return fmt.Sprintf(` +resource "aws_kinesis_analytics_application" "test" { + name = "testAcc-%d" + code = "testCode\n" + + tags = { + firstTag = "%s" + secondTag = "%s" + } +} +`, rInt, tag1, tag2) +} + +func testAccKinesisAnalyticsApplicationWithAddTags(rInt int, tag1, tag2, tag3 string) string { + return fmt.Sprintf(` +resource "aws_kinesis_analytics_application" "test" { + name = "testAcc-%d" + code = "testCode\n" + + tags = { + firstTag = "%s" + secondTag = "%s" + thirdTag = "%s" + } +} +`, rInt, tag1, tag2, tag3) +} diff --git a/aws/tagsKinesisAnalytics.go b/aws/tagsKinesisAnalytics.go new file mode 100644 index 00000000000..371e780a7f0 --- /dev/null +++ b/aws/tagsKinesisAnalytics.go @@ -0,0 +1,135 @@ +package aws + +import ( + "log" + "regexp" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/kinesisanalytics" + "github.com/hashicorp/terraform/helper/schema" +) + +// getTags is a helper to get the tags for a resource. It expects the +// tags field to be named "tags" and the ARN field to be named "arn". +func getTagsKinesisAnalytics(conn *kinesisanalytics.KinesisAnalytics, d *schema.ResourceData) error { + resp, err := conn.ListTagsForResource(&kinesisanalytics.ListTagsForResourceInput{ + ResourceARN: aws.String(d.Get("arn").(string)), + }) + if err != nil { + return err + } + + if err := d.Set("tags", tagsToMapKinesisAnalytics(resp.Tags)); err != nil { + return err + } + + return nil +} + +// setTags is a helper to set the tags for a resource. It expects the +// tags field to be named "tags" and the ARN field to be named "arn". +func setTagsKinesisAnalytics(conn *kinesisanalytics.KinesisAnalytics, 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 := diffTagsKinesisAnalytics(tagsFromMapKinesisAnalytics(o), tagsFromMapKinesisAnalytics(n)) + + // Set tags + if len(remove) > 0 { + log.Printf("[DEBUG] Removing tags: %#v", remove) + k := make([]*string, len(remove)) + for i, t := range remove { + k[i] = t.Key + } + + _, err := conn.UntagResource(&kinesisanalytics.UntagResourceInput{ + ResourceARN: aws.String(d.Get("arn").(string)), + TagKeys: k, + }) + if err != nil { + return err + } + } + if len(create) > 0 { + log.Printf("[DEBUG] Creating tags: %#v", create) + _, err := conn.TagResource(&kinesisanalytics.TagResourceInput{ + ResourceARN: aws.String(d.Get("arn").(string)), + 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 diffTagsKinesisAnalytics(oldTags, newTags []*kinesisanalytics.Tag) ([]*kinesisanalytics.Tag, []*kinesisanalytics.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 []*kinesisanalytics.Tag + for _, t := range oldTags { + old, ok := create[aws.StringValue(t.Key)] + if !ok || old != aws.StringValue(t.Value) { + remove = append(remove, t) + } else if ok { + // already present so remove from new + delete(create, aws.StringValue(t.Key)) + } + } + + return tagsFromMapKinesisAnalytics(create), remove +} + +// tagsFromMap returns the tags for the given map of data. +func tagsFromMapKinesisAnalytics(m map[string]interface{}) []*kinesisanalytics.Tag { + result := make([]*kinesisanalytics.Tag, 0, len(m)) + for k, v := range m { + t := &kinesisanalytics.Tag{ + Key: aws.String(k), + Value: aws.String(v.(string)), + } + if !tagIgnoredKinesisAnalytics(t) { + result = append(result, t) + } + } + + return result +} + +// tagsToMap turns the list of tags into a map. +func tagsToMapKinesisAnalytics(ts []*kinesisanalytics.Tag) map[string]string { + result := make(map[string]string) + for _, t := range ts { + if !tagIgnoredKinesisAnalytics(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 tagIgnoredKinesisAnalytics(t *kinesisanalytics.Tag) bool { + filter := []string{"^aws:"} + for _, v := range filter { + log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) + r, _ := regexp.MatchString(v, *t.Key) + if r { + log.Printf("[DEBUG] Found AWS specific tag %s (val: %s), ignoring.\n", *t.Key, *t.Value) + return true + } + } + return false +} diff --git a/aws/tagsKinesisAnalytics_test.go b/aws/tagsKinesisAnalytics_test.go new file mode 100644 index 00000000000..da0d2595b46 --- /dev/null +++ b/aws/tagsKinesisAnalytics_test.go @@ -0,0 +1,109 @@ +package aws + +import ( + "reflect" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/kinesisanalytics" +) + +func TestDiffKinesisAnalyticsTags(t *testing.T) { + cases := []struct { + Old, New map[string]interface{} + Create, Remove map[string]string + }{ + // Add + { + Old: map[string]interface{}{ + "foo": "bar", + }, + New: map[string]interface{}{ + "foo": "bar", + "bar": "baz", + }, + Create: map[string]string{ + "bar": "baz", + }, + Remove: map[string]string{}, + }, + + // 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 := diffTagsKinesisAnalytics(tagsFromMapKinesisAnalytics(tc.Old), tagsFromMapKinesisAnalytics(tc.New)) + cm := tagsToMapKinesisAnalytics(c) + rm := tagsToMapKinesisAnalytics(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) + } + } +} + +func TestIgnoringTagsKinesisAnalytics(t *testing.T) { + var ignoredTags []*kinesisanalytics.Tag + ignoredTags = append(ignoredTags, &kinesisanalytics.Tag{ + Key: aws.String("aws:cloudformation:logical-id"), + Value: aws.String("foo"), + }) + ignoredTags = append(ignoredTags, &kinesisanalytics.Tag{ + Key: aws.String("aws:foo:bar"), + Value: aws.String("baz"), + }) + for _, tag := range ignoredTags { + if !tagIgnoredKinesisAnalytics(tag) { + t.Fatalf("Tag %v with value %v not ignored, but should be!", *tag.Key, *tag.Value) + } + } +} diff --git a/website/docs/r/kinesis_analytics_application.html.markdown b/website/docs/r/kinesis_analytics_application.html.markdown index 58e8ace3b31..5586070f851 100644 --- a/website/docs/r/kinesis_analytics_application.html.markdown +++ b/website/docs/r/kinesis_analytics_application.html.markdown @@ -65,6 +65,7 @@ See [CloudWatch Logging Options](#cloudwatch-logging-options) below for more det * `outputs` - (Optional) Output destination configuration of the application. See [Outputs](#outputs) below for more details. * `reference_data_sources` - (Optional) An S3 Reference Data Source for the application. See [Reference Data Sources](#reference-data-sources) below for more details. +* `tags` - Key-value mapping of tags for the Kinesis Analytics Application. ### CloudWatch Logging Options