Skip to content

Commit

Permalink
r/aws_kinesis_analytics_application: Add support for resource tags
Browse files Browse the repository at this point in the history
  • Loading branch information
teraken0509 committed May 15, 2019
1 parent c4d48c0 commit e2c55b7
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 0 deletions.
14 changes: 14 additions & 0 deletions aws/resource_aws_kinesis_analytics_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ func resourceAwsKinesisAnalyticsApplication() *schema.Resource {
},
},
},
"tags": tagsSchema(),
},
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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")
Expand Down
85 changes: 85 additions & 0 deletions aws/resource_aws_kinesis_analytics_application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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)
}
135 changes: 135 additions & 0 deletions aws/tagsKinesisAnalytics.go
Original file line number Diff line number Diff line change
@@ -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
}
109 changes: 109 additions & 0 deletions aws/tagsKinesisAnalytics_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
1 change: 1 addition & 0 deletions website/docs/r/kinesis_analytics_application.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit e2c55b7

Please sign in to comment.