From 7625cce991d29eaeeecff71ecb70057eb1e798f7 Mon Sep 17 00:00:00 2001 From: Gianluca Cacace Date: Thu, 15 Apr 2021 00:33:22 +0100 Subject: [PATCH 1/3] Add support for aws_cloudwatch_metric_stream --- .changelog/18870.txt | 3 + aws/provider.go | 1 + aws/resource_aws_cloudwatch_metric_stream.go | 243 +++++++++ ...ource_aws_cloudwatch_metric_stream_test.go | 513 ++++++++++++++++++ .../r/cloudwatch_metric_stream.html.markdown | 174 ++++++ 5 files changed, 934 insertions(+) create mode 100644 .changelog/18870.txt create mode 100644 aws/resource_aws_cloudwatch_metric_stream.go create mode 100644 aws/resource_aws_cloudwatch_metric_stream_test.go create mode 100644 website/docs/r/cloudwatch_metric_stream.html.markdown diff --git a/.changelog/18870.txt b/.changelog/18870.txt new file mode 100644 index 00000000000..f7916bd72fd --- /dev/null +++ b/.changelog/18870.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_cloudwatch_metric_stream +``` \ No newline at end of file diff --git a/aws/provider.go b/aws/provider.go index 64ee387878e..51abdaa7b01 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -558,6 +558,7 @@ func Provider() *schema.Provider { "aws_cloudwatch_composite_alarm": resourceAwsCloudWatchCompositeAlarm(), "aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(), "aws_cloudwatch_dashboard": resourceAwsCloudWatchDashboard(), + "aws_cloudwatch_metric_stream": resourceAwsCloudWatchMetricStream(), "aws_cloudwatch_query_definition": resourceAwsCloudWatchQueryDefinition(), "aws_codedeploy_app": resourceAwsCodeDeployApp(), "aws_codedeploy_deployment_config": resourceAwsCodeDeployDeploymentConfig(), diff --git a/aws/resource_aws_cloudwatch_metric_stream.go b/aws/resource_aws_cloudwatch_metric_stream.go new file mode 100644 index 00000000000..f0a2b2f6fe4 --- /dev/null +++ b/aws/resource_aws_cloudwatch_metric_stream.go @@ -0,0 +1,243 @@ +package aws + +import ( + "fmt" + "log" + "regexp" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" +) + +func resourceAwsCloudWatchMetricStream() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsCloudWatchMetricStreamPut, + Read: resourceAwsCloudWatchMetricStreamRead, + Update: resourceAwsCloudWatchMetricStreamPut, + Delete: resourceAwsCloudWatchMetricStreamDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "creation_date": { + Type: schema.TypeString, + Computed: true, + }, + "exclude_filter": { + Type: schema.TypeSet, + Optional: true, + ConflictsWith: []string{"include_filter"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "namespace": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + }, + "firehose_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, + }, + "include_filter": { + Type: schema.TypeSet, + Optional: true, + ConflictsWith: []string{"exclude_filter"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "namespace": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + }, + }, + }, + "last_update_date": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"name_prefix"}, + ValidateFunc: validateCloudWatchMetricStreamName, + }, + "name_prefix": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"name"}, + ValidateFunc: validateCloudWatchMetricStreamName, + }, + "output_format": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchema(), + }, + } +} + +func resourceAwsCloudWatchMetricStreamRead(d *schema.ResourceData, meta interface{}) error { + name := d.Get("name").(string) + log.Printf("[DEBUG] Reading CloudWatch MetricStream: %s", name) + conn := meta.(*AWSClient).cloudwatchconn + + params := cloudwatch.GetMetricStreamInput{ + Name: aws.String(d.Id()), + } + + resp, err := conn.GetMetricStream(¶ms) + if err != nil { + if isAWSErr(err, cloudwatch.ErrCodeResourceNotFoundException, "") { + log.Printf("[WARN] CloudWatch MetricStream %q not found, removing", name) + d.SetId("") + return nil + } + return fmt.Errorf("Reading metric_stream failed: %s", err) + } + + d.Set("arn", resp.Arn) + d.Set("creation_date", resp.CreationDate.Format(time.RFC3339)) + d.Set("firehose_arn", resp.FirehoseArn) + d.Set("last_update_date", resp.CreationDate.Format(time.RFC3339)) + d.Set("name", resp.Name) + d.Set("output_format", resp.OutputFormat) + d.Set("role_arn", resp.RoleArn) + d.Set("state", resp.State) + + if resp.IncludeFilters != nil && len(resp.IncludeFilters) > 0 { + includeFilters := make([]interface{}, len(resp.IncludeFilters)) + for i, mq := range resp.IncludeFilters { + includeFilter := map[string]interface{}{ + "namespace": aws.StringValue(mq.Namespace), + } + includeFilters[i] = includeFilter + } + if err := d.Set("include_filter", includeFilters); err != nil { + return fmt.Errorf("error setting include_filter: %s", err) + } + } + + if resp.ExcludeFilters != nil && len(resp.ExcludeFilters) > 0 { + excludeFilters := make([]interface{}, len(resp.ExcludeFilters)) + for i, mq := range resp.ExcludeFilters { + excludeFilter := map[string]interface{}{ + "namespace": aws.StringValue(mq.Namespace), + } + excludeFilters[i] = excludeFilter + } + if err := d.Set("exclude_filter", excludeFilters); err != nil { + return fmt.Errorf("error setting exclude_filter: %s", err) + } + } + + return nil +} + +func resourceAwsCloudWatchMetricStreamPut(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).cloudwatchconn + + var name string + if v, ok := d.GetOk("name"); ok { + name = v.(string) + } else if v, ok := d.GetOk("name_prefix"); ok { + name = resource.PrefixedUniqueId(v.(string)) + } else { + name = resource.UniqueId() + } + + params := cloudwatch.PutMetricStreamInput{ + Name: aws.String(name), + FirehoseArn: aws.String(d.Get("firehose_arn").(string)), + RoleArn: aws.String(d.Get("role_arn").(string)), + OutputFormat: aws.String(d.Get("output_format").(string)), + Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().CloudwatchTags(), + } + + if v := d.Get("include_filter"); v != nil { + var includeFilters []*cloudwatch.MetricStreamFilter + for _, v := range v.(*schema.Set).List() { + metricStreamFilterResource := v.(map[string]interface{}) + namespace := metricStreamFilterResource["namespace"].(string) + metricStreamFilter := cloudwatch.MetricStreamFilter{ + Namespace: aws.String(namespace), + } + includeFilters = append(includeFilters, &metricStreamFilter) + } + params.IncludeFilters = includeFilters + } + + if v := d.Get("exclude_filter"); v != nil { + var excludeFilters []*cloudwatch.MetricStreamFilter + for _, v := range v.(*schema.Set).List() { + metricStreamFilterResource := v.(map[string]interface{}) + namespace := metricStreamFilterResource["namespace"].(string) + metricStreamFilter := cloudwatch.MetricStreamFilter{ + Namespace: aws.String(namespace), + } + excludeFilters = append(excludeFilters, &metricStreamFilter) + } + params.ExcludeFilters = excludeFilters + } + + log.Printf("[DEBUG] Putting CloudWatch MetricStream: %#v", params) + + _, err := conn.PutMetricStream(¶ms) + if err != nil { + return fmt.Errorf("Putting metric_stream failed: %s", err) + } + d.SetId(name) + log.Println("[INFO] CloudWatch MetricStream put finished") + + return resourceAwsCloudWatchMetricStreamRead(d, meta) +} + +func resourceAwsCloudWatchMetricStreamDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[INFO] Deleting CloudWatch MetricStream %s", d.Id()) + conn := meta.(*AWSClient).cloudwatchconn + params := cloudwatch.DeleteMetricStreamInput{ + Name: aws.String(d.Id()), + } + + if _, err := conn.DeleteMetricStream(¶ms); err != nil { + return fmt.Errorf("Error deleting CloudWatch MetricStream: %s", err) + } + log.Printf("[INFO] CloudWatch MetricStream %s deleted", d.Id()) + + return nil +} + +func validateCloudWatchMetricStreamName(v interface{}, k string) (ws []string, errors []error) { + return validation.All( + validation.StringLenBetween(1, 255), + validation.StringMatch(regexp.MustCompile(`^[\-_A-Za-z0-9]*$`), "must match [\\-_A-Za-z0-9]"), + )(v, k) +} diff --git a/aws/resource_aws_cloudwatch_metric_stream_test.go b/aws/resource_aws_cloudwatch_metric_stream_test.go new file mode 100644 index 00000000000..3e557196623 --- /dev/null +++ b/aws/resource_aws_cloudwatch_metric_stream_test.go @@ -0,0 +1,513 @@ +package aws + +import ( + "fmt" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccAWSCloudWatchMetricStream_basic(t *testing.T) { + var metricStream cloudwatch.GetMetricStreamOutput + resourceName := "aws_cloudwatch_metric_stream.test" + rInt := acctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, cloudwatch.EndpointsID), + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchMetricStreamConfig(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), + resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "output_format", "json"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCloudWatchMetricStream_noName(t *testing.T) { + var metricStream cloudwatch.GetMetricStreamOutput + resourceName := "aws_cloudwatch_metric_stream.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, cloudwatch.EndpointsID), + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchMetricStreamConfigNoName(), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCloudWatchMetricStream_namePrefix(t *testing.T) { + var metricStream cloudwatch.GetMetricStreamOutput + resourceName := "aws_cloudwatch_metric_stream.test" + rInt := acctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, cloudwatch.EndpointsID), + IDRefreshName: resourceName, + IDRefreshIgnore: []string{"name_prefix"}, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchMetricStreamConfigNamePrefix(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), + testAccCheckCloudWatchMetricStreamGeneratedNamePrefix(resourceName, "test-stream"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name_prefix"}, + }, + }, + }) +} + +func TestAccAWSCloudWatchMetricStream_includeFilters(t *testing.T) { + var metricStream cloudwatch.GetMetricStreamOutput + resourceName := "aws_cloudwatch_metric_stream.test" + rInt := acctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, cloudwatch.EndpointsID), + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchMetricStreamConfigIncludeFilters(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), + resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "output_format", "json"), + resource.TestCheckResourceAttr(resourceName, "include_filter.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCloudWatchMetricStream_excludeFilters(t *testing.T) { + var metricStream cloudwatch.GetMetricStreamOutput + resourceName := "aws_cloudwatch_metric_stream.test" + rInt := acctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, cloudwatch.EndpointsID), + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchMetricStreamConfigExcludeFilters(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), + resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "output_format", "json"), + resource.TestCheckResourceAttr(resourceName, "exclude_filter.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSCloudWatchMetricStream_update(t *testing.T) { + var metricStream cloudwatch.GetMetricStreamOutput + resourceName := "aws_cloudwatch_metric_stream.test" + rInt := acctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, cloudwatch.EndpointsID), + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchMetricStreamConfigUpdateArn(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), + resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "output_format", "json"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSCloudWatchMetricStreamConfig(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), + resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "output_format", "json"), + ), + }, + }, + }) +} + +func TestAccAWSCloudWatchMetricStream_updateName(t *testing.T) { + var metricStream cloudwatch.GetMetricStreamOutput + resourceName := "aws_cloudwatch_metric_stream.test" + rInt := acctest.RandInt() + rInt2 := acctest.RandInt() + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, cloudwatch.EndpointsID), + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchMetricStreamConfig(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), + resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + ), + }, + { + Config: testAccAWSCloudWatchMetricStreamConfig(rInt2), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), + resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt2)), + testAccCheckAWSCloudWatchMetricStreamDestroyPrevious(testAccAWSCloudWatchName(rInt)), + ), + }, + }, + }) +} + +func testAccCheckCloudWatchMetricStreamGeneratedNamePrefix(resource, prefix string) resource.TestCheckFunc { + return func(s *terraform.State) error { + r, ok := s.RootModule().Resources[resource] + if !ok { + return fmt.Errorf("Resource not found") + } + name, ok := r.Primary.Attributes["name"] + if !ok { + return fmt.Errorf("Name attr not found: %#v", r.Primary.Attributes) + } + if !strings.HasPrefix(name, prefix) { + return fmt.Errorf("Name: %q, does not have prefix: %q", name, prefix) + } + return nil + } +} + +func testAccCheckCloudWatchMetricStreamExists(n string, metricStream *cloudwatch.GetMetricStreamOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn + params := cloudwatch.GetMetricStreamInput{ + Name: aws.String(rs.Primary.ID), + } + + resp, err := conn.GetMetricStream(¶ms) + if err != nil { + return err + } + + *metricStream = *resp + + return nil + } +} + +func testAccCheckAWSCloudWatchMetricStreamDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloudwatch_metric_stream" { + continue + } + + params := cloudwatch.GetMetricStreamInput{ + Name: aws.String(rs.Primary.ID), + } + + _, err := conn.GetMetricStream(¶ms) + if err == nil { + return fmt.Errorf("MetricStream still exists: %s", rs.Primary.ID) + } + if !isAWSErr(err, cloudwatch.ErrCodeResourceNotFoundException, "") { + return err + } + } + + return nil +} + +func testAccCheckAWSCloudWatchMetricStreamDestroyPrevious(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn + + params := cloudwatch.GetMetricStreamInput{ + Name: aws.String(name), + } + + _, err := conn.GetMetricStream(¶ms) + + if err == nil { + return fmt.Errorf("MetricStream still exists: %s", name) + } + + if !isAWSErr(err, cloudwatch.ErrCodeResourceNotFoundException, "") { + return err + } + + return nil + } +} + +func testAccAWSCloudWatchName(rInt int) string { + return fmt.Sprintf("terraform-test-metric-stream-%d", rInt) +} + +func testAccAWSCloudWatchMetricStreamConfig(rInt int) string { + return fmt.Sprintf(` +resource "aws_cloudwatch_metric_stream" "test" { + name = "terraform-test-metric-stream-%d" + role_arn = aws_iam_role.metric_stream_to_firehose.arn + firehose_arn = aws_kinesis_firehose_delivery_stream.s3_stream.arn + output_format = "json" +} + +resource "aws_iam_role" "metric_stream_to_firehose" { + name = "metric_stream_to_firehose_role-%d" + + assume_role_policy = < +``` From f254525010bd880609beff3365eb80774ec6957a Mon Sep 17 00:00:00 2001 From: Gianluca Date: Fri, 16 Apr 2021 23:15:46 +0100 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: Dirk Avery <31492422+YakDriver@users.noreply.github.com> --- aws/resource_aws_cloudwatch_metric_stream.go | 6 +++--- aws/resource_aws_cloudwatch_metric_stream_test.go | 10 +++++----- website/docs/r/cloudwatch_metric_stream.html.markdown | 9 ++++----- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/aws/resource_aws_cloudwatch_metric_stream.go b/aws/resource_aws_cloudwatch_metric_stream.go index f0a2b2f6fe4..7efd533d1a9 100644 --- a/aws/resource_aws_cloudwatch_metric_stream.go +++ b/aws/resource_aws_cloudwatch_metric_stream.go @@ -162,7 +162,7 @@ func resourceAwsCloudWatchMetricStreamRead(d *schema.ResourceData, meta interfac return nil } -func resourceAwsCloudWatchMetricStreamPut(d *schema.ResourceData, meta interface{}) error { +func resourceAwsCloudWatchMetricStreamCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).cloudwatchconn var name string @@ -182,7 +182,7 @@ func resourceAwsCloudWatchMetricStreamPut(d *schema.ResourceData, meta interface Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().CloudwatchTags(), } - if v := d.Get("include_filter"); v != nil { + if v, ok := d.GetOk("include_filter"); ok { var includeFilters []*cloudwatch.MetricStreamFilter for _, v := range v.(*schema.Set).List() { metricStreamFilterResource := v.(map[string]interface{}) @@ -195,7 +195,7 @@ func resourceAwsCloudWatchMetricStreamPut(d *schema.ResourceData, meta interface params.IncludeFilters = includeFilters } - if v := d.Get("exclude_filter"); v != nil { + if v, ok := d.GetOk("exclude_filter"); ok { var excludeFilters []*cloudwatch.MetricStreamFilter for _, v := range v.(*schema.Set).List() { metricStreamFilterResource := v.(map[string]interface{}) diff --git a/aws/resource_aws_cloudwatch_metric_stream_test.go b/aws/resource_aws_cloudwatch_metric_stream_test.go index 3e557196623..6508a606b2f 100644 --- a/aws/resource_aws_cloudwatch_metric_stream_test.go +++ b/aws/resource_aws_cloudwatch_metric_stream_test.go @@ -15,7 +15,7 @@ import ( func TestAccAWSCloudWatchMetricStream_basic(t *testing.T) { var metricStream cloudwatch.GetMetricStreamOutput resourceName := "aws_cloudwatch_metric_stream.test" - rInt := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -24,7 +24,7 @@ func TestAccAWSCloudWatchMetricStream_basic(t *testing.T) { CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCloudWatchMetricStreamConfig(rInt), + Config: testAccAWSCloudWatchMetricStreamConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), @@ -309,7 +309,7 @@ func testAccAWSCloudWatchName(rInt int) string { return fmt.Sprintf("terraform-test-metric-stream-%d", rInt) } -func testAccAWSCloudWatchMetricStreamConfig(rInt int) string { +func testAccAWSCloudWatchMetricStreamConfig(rName string) string { return fmt.Sprintf(` resource "aws_cloudwatch_metric_stream" "test" { name = "terraform-test-metric-stream-%d" @@ -319,7 +319,7 @@ resource "aws_cloudwatch_metric_stream" "test" { } resource "aws_iam_role" "metric_stream_to_firehose" { - name = "metric_stream_to_firehose_role-%d" + name = %[1]q assume_role_policy = < Date: Sat, 17 Apr 2021 00:34:02 +0100 Subject: [PATCH 3/3] Addressed feedback from PR --- .../service/cloudwatch/waiter/status.go | 28 +++ .../service/cloudwatch/waiter/waiter.go | 29 +++ aws/resource_aws_cloudwatch_metric_stream.go | 221 ++++++++++-------- ...ource_aws_cloudwatch_metric_stream_test.go | 86 ++++--- .../r/cloudwatch_metric_stream.html.markdown | 20 +- 5 files changed, 240 insertions(+), 144 deletions(-) create mode 100644 aws/internal/service/cloudwatch/waiter/status.go create mode 100644 aws/internal/service/cloudwatch/waiter/waiter.go diff --git a/aws/internal/service/cloudwatch/waiter/status.go b/aws/internal/service/cloudwatch/waiter/status.go new file mode 100644 index 00000000000..fc84d760e85 --- /dev/null +++ b/aws/internal/service/cloudwatch/waiter/status.go @@ -0,0 +1,28 @@ +package waiter + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func MetricStreamState(ctx context.Context, conn *cloudwatch.CloudWatch, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + input := cloudwatch.GetMetricStreamInput{ + Name: aws.String(name), + } + + metricStream, err := conn.GetMetricStreamWithContext(ctx, &input) + if err != nil { + if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFoundException) { + return nil, "", nil + } + return nil, "", err + } + + return metricStream, aws.StringValue(metricStream.State), err + } +} diff --git a/aws/internal/service/cloudwatch/waiter/waiter.go b/aws/internal/service/cloudwatch/waiter/waiter.go new file mode 100644 index 00000000000..064ee320e2b --- /dev/null +++ b/aws/internal/service/cloudwatch/waiter/waiter.go @@ -0,0 +1,29 @@ +package waiter + +import ( + "context" + "time" + + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func MetricStreamDeleted(ctx context.Context, conn *cloudwatch.CloudWatch, name string) (*cloudwatch.GetMetricStreamOutput, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + "running", + "stopped", + }, + Target: []string{}, + Refresh: MetricStreamState(ctx, conn, name), + Timeout: 10 * time.Minute, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if v, ok := outputRaw.(*cloudwatch.GetMetricStreamOutput); ok { + return v, err + } + + return nil, err +} diff --git a/aws/resource_aws_cloudwatch_metric_stream.go b/aws/resource_aws_cloudwatch_metric_stream.go index 7efd533d1a9..345cfab6f47 100644 --- a/aws/resource_aws_cloudwatch_metric_stream.go +++ b/aws/resource_aws_cloudwatch_metric_stream.go @@ -1,6 +1,7 @@ package aws import ( + "context" "fmt" "log" "regexp" @@ -8,23 +9,31 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/cloudwatch/waiter" ) func resourceAwsCloudWatchMetricStream() *schema.Resource { return &schema.Resource{ - Create: resourceAwsCloudWatchMetricStreamPut, - Read: resourceAwsCloudWatchMetricStreamRead, - Update: resourceAwsCloudWatchMetricStreamPut, - Delete: resourceAwsCloudWatchMetricStreamDelete, + CreateContext: resourceAwsCloudWatchMetricStreamCreate, + ReadContext: resourceAwsCloudWatchMetricStreamRead, + UpdateContext: resourceAwsCloudWatchMetricStreamCreate, + DeleteContext: resourceAwsCloudWatchMetricStreamDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(1 * time.Minute), + Delete: schema.DefaultTimeout(1 * time.Minute), + }, + Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, @@ -105,64 +114,7 @@ func resourceAwsCloudWatchMetricStream() *schema.Resource { } } -func resourceAwsCloudWatchMetricStreamRead(d *schema.ResourceData, meta interface{}) error { - name := d.Get("name").(string) - log.Printf("[DEBUG] Reading CloudWatch MetricStream: %s", name) - conn := meta.(*AWSClient).cloudwatchconn - - params := cloudwatch.GetMetricStreamInput{ - Name: aws.String(d.Id()), - } - - resp, err := conn.GetMetricStream(¶ms) - if err != nil { - if isAWSErr(err, cloudwatch.ErrCodeResourceNotFoundException, "") { - log.Printf("[WARN] CloudWatch MetricStream %q not found, removing", name) - d.SetId("") - return nil - } - return fmt.Errorf("Reading metric_stream failed: %s", err) - } - - d.Set("arn", resp.Arn) - d.Set("creation_date", resp.CreationDate.Format(time.RFC3339)) - d.Set("firehose_arn", resp.FirehoseArn) - d.Set("last_update_date", resp.CreationDate.Format(time.RFC3339)) - d.Set("name", resp.Name) - d.Set("output_format", resp.OutputFormat) - d.Set("role_arn", resp.RoleArn) - d.Set("state", resp.State) - - if resp.IncludeFilters != nil && len(resp.IncludeFilters) > 0 { - includeFilters := make([]interface{}, len(resp.IncludeFilters)) - for i, mq := range resp.IncludeFilters { - includeFilter := map[string]interface{}{ - "namespace": aws.StringValue(mq.Namespace), - } - includeFilters[i] = includeFilter - } - if err := d.Set("include_filter", includeFilters); err != nil { - return fmt.Errorf("error setting include_filter: %s", err) - } - } - - if resp.ExcludeFilters != nil && len(resp.ExcludeFilters) > 0 { - excludeFilters := make([]interface{}, len(resp.ExcludeFilters)) - for i, mq := range resp.ExcludeFilters { - excludeFilter := map[string]interface{}{ - "namespace": aws.StringValue(mq.Namespace), - } - excludeFilters[i] = excludeFilter - } - if err := d.Set("exclude_filter", excludeFilters); err != nil { - return fmt.Errorf("error setting exclude_filter: %s", err) - } - } - - return nil -} - -func resourceAwsCloudWatchMetricStreamCreate(d *schema.ResourceData, meta interface{}) error { +func resourceAwsCloudWatchMetricStreamCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*AWSClient).cloudwatchconn var name string @@ -182,54 +134,105 @@ func resourceAwsCloudWatchMetricStreamCreate(d *schema.ResourceData, meta interf Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().CloudwatchTags(), } - if v, ok := d.GetOk("include_filter"); ok { - var includeFilters []*cloudwatch.MetricStreamFilter - for _, v := range v.(*schema.Set).List() { - metricStreamFilterResource := v.(map[string]interface{}) - namespace := metricStreamFilterResource["namespace"].(string) - metricStreamFilter := cloudwatch.MetricStreamFilter{ - Namespace: aws.String(namespace), - } - includeFilters = append(includeFilters, &metricStreamFilter) - } - params.IncludeFilters = includeFilters + if v, ok := d.GetOk("include_filter"); ok && v.(*schema.Set).Len() > 0 { + params.IncludeFilters = expandCloudWatchMetricStreamFilters(v.(*schema.Set)) } - if v, ok := d.GetOk("exclude_filter"); ok { - var excludeFilters []*cloudwatch.MetricStreamFilter - for _, v := range v.(*schema.Set).List() { - metricStreamFilterResource := v.(map[string]interface{}) - namespace := metricStreamFilterResource["namespace"].(string) - metricStreamFilter := cloudwatch.MetricStreamFilter{ - Namespace: aws.String(namespace), - } - excludeFilters = append(excludeFilters, &metricStreamFilter) - } - params.ExcludeFilters = excludeFilters + if v, ok := d.GetOk("exclude_filter"); ok && v.(*schema.Set).Len() > 0 { + params.ExcludeFilters = expandCloudWatchMetricStreamFilters(v.(*schema.Set)) } log.Printf("[DEBUG] Putting CloudWatch MetricStream: %#v", params) - - _, err := conn.PutMetricStream(¶ms) + _, err := conn.PutMetricStreamWithContext(ctx, ¶ms) if err != nil { - return fmt.Errorf("Putting metric_stream failed: %s", err) + return diag.FromErr(fmt.Errorf("Putting metric_stream failed: %s", err)) } d.SetId(name) log.Println("[INFO] CloudWatch MetricStream put finished") - return resourceAwsCloudWatchMetricStreamRead(d, meta) + return resourceAwsCloudWatchMetricStreamRead(ctx, d, meta) } -func resourceAwsCloudWatchMetricStreamDelete(d *schema.ResourceData, meta interface{}) error { +func resourceAwsCloudWatchMetricStreamRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + name := d.Get("name").(string) + log.Printf("[DEBUG] Reading CloudWatch MetricStream: %s", name) + conn := meta.(*AWSClient).cloudwatchconn + + params := cloudwatch.GetMetricStreamInput{ + Name: aws.String(d.Id()), + } + + var err error + var resp *cloudwatch.GetMetricStreamOutput + + if d.IsNewResource() { + err = resource.RetryContext(ctx, d.Timeout(schema.TimeoutRead), func() *resource.RetryError { + resp, err = conn.GetMetricStreamWithContext(ctx, ¶ms) + if err != nil { + if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFoundException) { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + if isResourceTimeoutError(err) { + resp, err = conn.GetMetricStreamWithContext(ctx, ¶ms) + } + } else { + resp, err = conn.GetMetricStreamWithContext(ctx, ¶ms) + if err != nil { + if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFoundException) { + log.Printf("[WARN] CloudWatch MetricStream (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + } + } + + if err != nil { + return diag.FromErr(fmt.Errorf("Reading metric_stream failed: %s", err)) + } + + d.Set("arn", resp.Arn) + d.Set("creation_date", resp.CreationDate.Format(time.RFC3339)) + d.Set("firehose_arn", resp.FirehoseArn) + d.Set("last_update_date", resp.CreationDate.Format(time.RFC3339)) + d.Set("name", resp.Name) + d.Set("output_format", resp.OutputFormat) + d.Set("role_arn", resp.RoleArn) + d.Set("state", resp.State) + + if resp.IncludeFilters != nil { + if err := d.Set("include_filter", flattenCloudWatchMetricStreamFilter(resp.IncludeFilters)); err != nil { + return diag.FromErr(fmt.Errorf("error setting include_filter error: %w", err)) + } + } + + if resp.ExcludeFilters != nil { + if err := d.Set("exclude_filter", flattenCloudWatchMetricStreamFilter(resp.ExcludeFilters)); err != nil { + return diag.FromErr(fmt.Errorf("error setting exclude_filter error: %w", err)) + } + } + + return nil +} + +func resourceAwsCloudWatchMetricStreamDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { log.Printf("[INFO] Deleting CloudWatch MetricStream %s", d.Id()) conn := meta.(*AWSClient).cloudwatchconn params := cloudwatch.DeleteMetricStreamInput{ Name: aws.String(d.Id()), } - if _, err := conn.DeleteMetricStream(¶ms); err != nil { - return fmt.Errorf("Error deleting CloudWatch MetricStream: %s", err) + if _, err := conn.DeleteMetricStreamWithContext(ctx, ¶ms); err != nil { + return diag.FromErr(fmt.Errorf("Error deleting CloudWatch MetricStream: %s", err)) } + + if _, err := waiter.MetricStreamDeleted(ctx, conn, d.Id()); err != nil { + return diag.FromErr(fmt.Errorf("error while waiting for CloudWatch Metric Stream (%s) to become deleted: %w", d.Id(), err)) + } + log.Printf("[INFO] CloudWatch MetricStream %s deleted", d.Id()) return nil @@ -241,3 +244,39 @@ func validateCloudWatchMetricStreamName(v interface{}, k string) (ws []string, e validation.StringMatch(regexp.MustCompile(`^[\-_A-Za-z0-9]*$`), "must match [\\-_A-Za-z0-9]"), )(v, k) } + +func expandCloudWatchMetricStreamFilters(s *schema.Set) []*cloudwatch.MetricStreamFilter { + var filters []*cloudwatch.MetricStreamFilter + + for _, filterRaw := range s.List() { + filter := &cloudwatch.MetricStreamFilter{} + mFilter := filterRaw.(map[string]interface{}) + + if v, ok := mFilter["namespace"].(string); ok && v != "" { + filter.Namespace = aws.String(v) + } + + filters = append(filters, filter) + } + + return filters +} + +func flattenCloudWatchMetricStreamFilter(s []*cloudwatch.MetricStreamFilter) []map[string]interface{} { + filters := make([]map[string]interface{}, 0) + + for _, bd := range s { + if bd.Namespace != nil { + stage := make(map[string]interface{}) + stage["namespace"] = aws.StringValue(bd.Namespace) + + filters = append(filters, stage) + } + } + + if len(filters) > 0 { + return filters + } + + return nil +} diff --git a/aws/resource_aws_cloudwatch_metric_stream_test.go b/aws/resource_aws_cloudwatch_metric_stream_test.go index 6508a606b2f..4cc04a3ac22 100644 --- a/aws/resource_aws_cloudwatch_metric_stream_test.go +++ b/aws/resource_aws_cloudwatch_metric_stream_test.go @@ -27,7 +27,7 @@ func TestAccAWSCloudWatchMetricStream_basic(t *testing.T) { Config: testAccAWSCloudWatchMetricStreamConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), - resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "output_format", "json"), ), }, @@ -68,7 +68,7 @@ func TestAccAWSCloudWatchMetricStream_noName(t *testing.T) { func TestAccAWSCloudWatchMetricStream_namePrefix(t *testing.T) { var metricStream cloudwatch.GetMetricStreamOutput resourceName := "aws_cloudwatch_metric_stream.test" - rInt := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -79,10 +79,10 @@ func TestAccAWSCloudWatchMetricStream_namePrefix(t *testing.T) { CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCloudWatchMetricStreamConfigNamePrefix(rInt), + Config: testAccAWSCloudWatchMetricStreamConfigNamePrefix(rName), Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), - testAccCheckCloudWatchMetricStreamGeneratedNamePrefix(resourceName, "test-stream"), + testAccCheckCloudWatchMetricStreamGeneratedNamePrefix(resourceName, "tf-acc-test"), ), }, { @@ -98,7 +98,7 @@ func TestAccAWSCloudWatchMetricStream_namePrefix(t *testing.T) { func TestAccAWSCloudWatchMetricStream_includeFilters(t *testing.T) { var metricStream cloudwatch.GetMetricStreamOutput resourceName := "aws_cloudwatch_metric_stream.test" - rInt := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -107,10 +107,10 @@ func TestAccAWSCloudWatchMetricStream_includeFilters(t *testing.T) { CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCloudWatchMetricStreamConfigIncludeFilters(rInt), + Config: testAccAWSCloudWatchMetricStreamConfigIncludeFilters(rName), Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), - resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "output_format", "json"), resource.TestCheckResourceAttr(resourceName, "include_filter.#", "2"), ), @@ -127,7 +127,7 @@ func TestAccAWSCloudWatchMetricStream_includeFilters(t *testing.T) { func TestAccAWSCloudWatchMetricStream_excludeFilters(t *testing.T) { var metricStream cloudwatch.GetMetricStreamOutput resourceName := "aws_cloudwatch_metric_stream.test" - rInt := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -136,10 +136,10 @@ func TestAccAWSCloudWatchMetricStream_excludeFilters(t *testing.T) { CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCloudWatchMetricStreamConfigExcludeFilters(rInt), + Config: testAccAWSCloudWatchMetricStreamConfigExcludeFilters(rName), Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), - resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "output_format", "json"), resource.TestCheckResourceAttr(resourceName, "exclude_filter.#", "2"), ), @@ -156,7 +156,7 @@ func TestAccAWSCloudWatchMetricStream_excludeFilters(t *testing.T) { func TestAccAWSCloudWatchMetricStream_update(t *testing.T) { var metricStream cloudwatch.GetMetricStreamOutput resourceName := "aws_cloudwatch_metric_stream.test" - rInt := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -165,10 +165,10 @@ func TestAccAWSCloudWatchMetricStream_update(t *testing.T) { CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCloudWatchMetricStreamConfigUpdateArn(rInt), + Config: testAccAWSCloudWatchMetricStreamConfigUpdateArn(rName), Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), - resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "output_format", "json"), ), }, @@ -178,10 +178,10 @@ func TestAccAWSCloudWatchMetricStream_update(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAWSCloudWatchMetricStreamConfig(rInt), + Config: testAccAWSCloudWatchMetricStreamConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), - resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "output_format", "json"), ), }, @@ -192,8 +192,8 @@ func TestAccAWSCloudWatchMetricStream_update(t *testing.T) { func TestAccAWSCloudWatchMetricStream_updateName(t *testing.T) { var metricStream cloudwatch.GetMetricStreamOutput resourceName := "aws_cloudwatch_metric_stream.test" - rInt := acctest.RandInt() - rInt2 := acctest.RandInt() + rName := acctest.RandomWithPrefix("tf-acc-test") + rName2 := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ErrorCheck: testAccErrorCheck(t, cloudwatch.EndpointsID), @@ -201,18 +201,18 @@ func TestAccAWSCloudWatchMetricStream_updateName(t *testing.T) { CheckDestroy: testAccCheckAWSCloudWatchMetricStreamDestroy, Steps: []resource.TestStep{ { - Config: testAccAWSCloudWatchMetricStreamConfig(rInt), + Config: testAccAWSCloudWatchMetricStreamConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), - resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, { - Config: testAccAWSCloudWatchMetricStreamConfig(rInt2), + Config: testAccAWSCloudWatchMetricStreamConfig(rName2), Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchMetricStreamExists(resourceName, &metricStream), - resource.TestCheckResourceAttr(resourceName, "name", testAccAWSCloudWatchName(rInt2)), - testAccCheckAWSCloudWatchMetricStreamDestroyPrevious(testAccAWSCloudWatchName(rInt)), + resource.TestCheckResourceAttr(resourceName, "name", rName2), + testAccCheckAWSCloudWatchMetricStreamDestroyPrevious(rName), ), }, }, @@ -305,14 +305,12 @@ func testAccCheckAWSCloudWatchMetricStreamDestroyPrevious(name string) resource. } } -func testAccAWSCloudWatchName(rInt int) string { - return fmt.Sprintf("terraform-test-metric-stream-%d", rInt) -} - func testAccAWSCloudWatchMetricStreamConfig(rName string) string { return fmt.Sprintf(` +data "aws_partition" "current" {} + resource "aws_cloudwatch_metric_stream" "test" { - name = "terraform-test-metric-stream-%d" + name = %[1]q role_arn = aws_iam_role.metric_stream_to_firehose.arn firehose_arn = aws_kinesis_firehose_delivery_stream.s3_stream.arn output_format = "json" @@ -328,7 +326,7 @@ resource "aws_iam_role" "metric_stream_to_firehose" { { "Action": "sts:AssumeRole", "Principal": { - "Service": "streams.metrics.cloudwatch.amazonaws.com" + "Service": "streams.metrics.cloudwatch.${data.aws_partition.current.dns_suffix}" }, "Effect": "Allow", "Sid": "" @@ -360,7 +358,7 @@ EOF } resource "aws_s3_bucket" "bucket" { - bucket = "metric-stream-test-bucket-%d" + bucket = %[1]q acl = "private" } @@ -372,7 +370,7 @@ resource "aws_iam_role" "firehose_to_s3" { { "Action": "sts:AssumeRole", "Principal": { - "Service": "firehose.amazonaws.com" + "Service": "firehose.${data.aws_partition.current.dns_suffix}" }, "Effect": "Allow", "Sid": "" @@ -411,7 +409,7 @@ EOF } resource "aws_kinesis_firehose_delivery_stream" "s3_stream" { - name = "metric-stream-test-stream-%d" + name = %[1]q destination = "s3" s3_configuration { @@ -419,32 +417,32 @@ resource "aws_kinesis_firehose_delivery_stream" "s3_stream" { bucket_arn = aws_s3_bucket.bucket.arn } } -`, rInt, rInt, rInt, rInt) +`, rName) } -func testAccAWSCloudWatchMetricStreamConfigUpdateArn(rInt int) string { +func testAccAWSCloudWatchMetricStreamConfigUpdateArn(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} data "aws_region" "current" {} data "aws_caller_identity" "current" {} resource "aws_cloudwatch_metric_stream" "test" { - name = "terraform-test-metric-stream-%d" + name = %q role_arn = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:role/MyOtherRole" firehose_arn = "arn:${data.aws_partition.current.partition}:firehose:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:deliverystream/MyOtherFirehose" output_format = "json" } -`, rInt) +`, rName) } -func testAccAWSCloudWatchMetricStreamConfigIncludeFilters(rInt int) string { +func testAccAWSCloudWatchMetricStreamConfigIncludeFilters(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} data "aws_region" "current" {} data "aws_caller_identity" "current" {} resource "aws_cloudwatch_metric_stream" "test" { - name = "terraform-test-metric-stream-%d" + name = %q role_arn = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:role/MyRole" firehose_arn = "arn:${data.aws_partition.current.partition}:firehose:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:deliverystream/MyFirehose" output_format = "json" @@ -457,7 +455,7 @@ resource "aws_cloudwatch_metric_stream" "test" { namespace = "AWS/EBS" } } -`, rInt) +`, rName) } func testAccAWSCloudWatchMetricStreamConfigNoName() string { @@ -474,29 +472,29 @@ resource "aws_cloudwatch_metric_stream" "test" { ` } -func testAccAWSCloudWatchMetricStreamConfigNamePrefix(rInt int) string { +func testAccAWSCloudWatchMetricStreamConfigNamePrefix(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} data "aws_region" "current" {} data "aws_caller_identity" "current" {} resource "aws_cloudwatch_metric_stream" "test" { - name_prefix = "tf-acc-test" + name_prefix = %q role_arn = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:role/MyRole" firehose_arn = "arn:${data.aws_partition.current.partition}:firehose:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:deliverystream/MyFirehose" output_format = "json" } -`, rInt) +`, rName) } -func testAccAWSCloudWatchMetricStreamConfigExcludeFilters(rInt int) string { +func testAccAWSCloudWatchMetricStreamConfigExcludeFilters(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} data "aws_region" "current" {} data "aws_caller_identity" "current" {} resource "aws_cloudwatch_metric_stream" "test" { - name = "terraform-test-metric-stream-%d" + name = %q role_arn = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:role/MyRole" firehose_arn = "arn:${data.aws_partition.current.partition}:firehose:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:deliverystream/MyFirehose" output_format = "json" @@ -509,5 +507,5 @@ resource "aws_cloudwatch_metric_stream" "test" { namespace = "AWS/EBS" } } -`, rInt) +`, rName) } diff --git a/website/docs/r/cloudwatch_metric_stream.html.markdown b/website/docs/r/cloudwatch_metric_stream.html.markdown index f867612e7ec..4e226bf554b 100644 --- a/website/docs/r/cloudwatch_metric_stream.html.markdown +++ b/website/docs/r/cloudwatch_metric_stream.html.markdown @@ -135,25 +135,27 @@ resource "aws_kinesis_firehose_delivery_stream" "s3_stream" { ## Argument Reference -The following arguments are supported: +The following arguments are required: + +* `firehose_arn` - (Required) ARN of the Amazon Kinesis Firehose delivery stream to use for this metric stream. +* `role_arn` - (Required) ARN of the IAM role that this metric stream will use to access Amazon Kinesis Firehose resources. For more information about role permissions, see [Trust between CloudWatch and Kinesis Data Firehose](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-metric-streams-trustpolicy.html). +* `output_format` - (Required) Output format for the stream. For more information about output formats, see [Metric streams output formats](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-metric-streams-formats.html). +* `tags` - (Optional) Map of tags to assign to the resource. + +The following arguments are optional: * `exclude_filter` - (Optional) List of exclusive metric filters. If you specify this parameter, the stream sends metrics from all metric namespaces except for the namespaces that you specify here. Conflicts with `include_filter`. -* `firehose_arn` - (Required) The Amazon Resource Name (ARN) of the Amazon Kinesis Firehose delivery stream to use for this metric stream. * `include_filter` - (Optional) List of inclusive metric filters. If you specify this parameter, the stream sends only the metrics from the metric namespaces that you specify here. Conflicts with `exclude_filter`. * `name` - (Optional, Forces new resource) Friendly name of the metric stream. If omitted, Terraform will assign a random, unique name. Conflicts with `name_prefix`. * `name_prefix` - (Optional, Forces new resource) Creates a unique friendly name beginning with the specified prefix. Conflicts with `name`. -* `role_arn` - (Required) The Amazon Resource Name (ARN) of the IAM role that this metric stream will use to access Amazon Kinesis Firehose resources. -* `output_format` - (Required) Output format for the stream. For more information about metric stream output formats, see [Metric streams output formats](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-metric-streams-formats.html). -* `tags` - (Optional) A map of tags to assign to the resource. - ### `exclude_filter` -* `namespace` - (Required) The name of the metric namespace in the filter. +* `namespace` - (Required) Name of the metric namespace in the filter. ### `include_filter` -* `namespace` - (Required) The name of the metric namespace in the filter. +* `namespace` - (Required) Name of the metric namespace in the filter. ## Attributes Reference @@ -162,7 +164,7 @@ In addition to all arguments above, the following attributes are exported: * `arn` - ARN of the metric stream. * `creation_date` - Date and time in [RFC3339 format](https://tools.ietf.org/html/rfc3339#section-5.8) that the metric stream was created. * `last_update_date` - Date and time in [RFC3339 format](https://tools.ietf.org/html/rfc3339#section-5.8) that the metric stream was last updated. -* `state` - The state of the metric stream. The possible values are `running` and `stopped`. +* `state` - State of the metric stream. Possible values are `running` and `stopped`. ## Import