diff --git a/.changelog/28310.txt b/.changelog/28310.txt new file mode 100644 index 00000000000..a3e129dfd46 --- /dev/null +++ b/.changelog/28310.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_cloudwatch_metric_stream: Correctly update `tags` +``` diff --git a/internal/service/cloudwatch/metric_stream.go b/internal/service/cloudwatch/metric_stream.go index eeb493c7752..26dc7c59086 100644 --- a/internal/service/cloudwatch/metric_stream.go +++ b/internal/service/cloudwatch/metric_stream.go @@ -2,7 +2,6 @@ package cloudwatch import ( "context" - "fmt" "log" "regexp" "time" @@ -11,29 +10,32 @@ import ( "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/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/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) func ResourceMetricStream() *schema.Resource { return &schema.Resource{ - CreateContext: resourceMetricStreamCreate, - ReadContext: resourceMetricStreamRead, - UpdateContext: resourceMetricStreamCreate, - DeleteContext: resourceMetricStreamDelete, + CreateWithoutTimeout: resourceMetricStreamCreate, + ReadWithoutTimeout: resourceMetricStreamRead, + UpdateWithoutTimeout: resourceMetricStreamUpdate, + DeleteWithoutTimeout: resourceMetricStreamDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, Timeouts: &schema.ResourceTimeout{ - Create: schema.DefaultTimeout(MetricStreamReadyTimeout), - Delete: schema.DefaultTimeout(MetricStreamDeleteTimeout), + Create: schema.DefaultTimeout(1 * time.Minute), + Update: schema.DefaultTimeout(1 * time.Minute), + Delete: schema.DefaultTimeout(2 * time.Minute), }, CustomizeDiff: verify.SetTagsDiff, @@ -175,51 +177,52 @@ func resourceMetricStreamCreate(ctx context.Context, d *schema.ResourceData, met tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) name := create.Name(d.Get("name").(string), d.Get("name_prefix").(string)) - - params := cloudwatch.PutMetricStreamInput{ - Name: aws.String(name), + input := &cloudwatch.PutMetricStreamInput{ FirehoseArn: aws.String(d.Get("firehose_arn").(string)), - RoleArn: aws.String(d.Get("role_arn").(string)), + Name: aws.String(name), OutputFormat: aws.String(d.Get("output_format").(string)), + RoleArn: aws.String(d.Get("role_arn").(string)), } - if len(tags) > 0 { - params.Tags = Tags(tags.IgnoreAWS()) + if v, ok := d.GetOk("exclude_filter"); ok && v.(*schema.Set).Len() > 0 { + input.ExcludeFilters = expandMetricStreamFilters(v.(*schema.Set)) } if v, ok := d.GetOk("include_filter"); ok && v.(*schema.Set).Len() > 0 { - params.IncludeFilters = expandMetricStreamFilters(v.(*schema.Set)) + input.IncludeFilters = expandMetricStreamFilters(v.(*schema.Set)) } - if v, ok := d.GetOk("exclude_filter"); ok && v.(*schema.Set).Len() > 0 { - params.ExcludeFilters = expandMetricStreamFilters(v.(*schema.Set)) + if v, ok := d.GetOk("statistics_configuration"); ok && v.(*schema.Set).Len() > 0 { + input.StatisticsConfigurations = expandMetricStreamStatisticsConfigurations(v.(*schema.Set)) } - if v, ok := d.GetOk("statistics_configuration"); ok && v.(*schema.Set).Len() > 0 { - params.StatisticsConfigurations = expandMetricStreamStatisticsConfigurations(v.(*schema.Set)) + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) } - log.Printf("[DEBUG] Putting CloudWatch Metric Stream: %#v", params) - output, err := conn.PutMetricStreamWithContext(ctx, ¶ms) + output, err := conn.PutMetricStreamWithContext(ctx, input) // Some partitions (i.e., ISO) may not support tag-on-create - if params.Tags != nil && verify.ErrorISOUnsupported(conn.PartitionID, err) { + if input.Tags != nil && verify.ErrorISOUnsupported(conn.PartitionID, err) { log.Printf("[WARN] failed creating CloudWatch Metric Stream (%s) with tags: %s. Trying create without tags.", name, err) - params.Tags = nil + input.Tags = nil - output, err = conn.PutMetricStreamWithContext(ctx, ¶ms) + output, err = conn.PutMetricStreamWithContext(ctx, input) } if err != nil { - return diag.Errorf("failed creating CloudWatch Metric Stream (%s): %s", name, err) + return diag.Errorf("creating CloudWatch Metric Stream (%s): %s", name, err) } d.SetId(name) - log.Println("[INFO] CloudWatch Metric Stream put finished") + + if _, err := waitMetricStreamRunning(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("waiting for CloudWatch Metric Stream (%s) create: %s", d.Id(), err) + } // Some partitions (i.e., ISO) may not support tag-on-create, attempt tag after create - if params.Tags == nil && len(tags) > 0 { - err := UpdateTags(conn, aws.StringValue(output.Arn), nil, tags) + if input.Tags == nil && len(tags) > 0 { + err := UpdateTagsWithContext(ctx, conn, aws.StringValue(output.Arn), nil, tags) // If default tags only, log and continue. Otherwise, error. if v, ok := d.GetOk("tags"); (!ok || len(v.(map[string]interface{})) == 0) && verify.ErrorISOUnsupported(conn.PartitionID, err) { @@ -228,7 +231,7 @@ func resourceMetricStreamCreate(ctx context.Context, d *schema.ResourceData, met } if err != nil { - return diag.Errorf("failed adding tags after create for CloudWatch Metric Stream (%s): %s", d.Id(), err) + return diag.Errorf("adding tags after create for CloudWatch Metric Stream (%s): %s", d.Id(), err) } } @@ -240,20 +243,16 @@ func resourceMetricStreamRead(ctx context.Context, d *schema.ResourceData, meta defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - output, err := WaitMetricStreamReady(ctx, conn, d.Id()) + output, err := FindMetricStreamByName(ctx, conn, d.Id()) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFoundException) { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] CloudWatch Metric Stream (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return diag.FromErr(fmt.Errorf("error getting CloudWatch Metric Stream (%s): %w", d.Id(), err)) - } - - if output == nil { - return diag.FromErr(fmt.Errorf("error getting CloudWatch Metric Stream (%s): empty response", d.Id())) + return diag.Errorf("reading CloudWatch Metric Stream (%s): %s", d.Id(), err) } d.Set("arn", output.Arn) @@ -268,23 +267,23 @@ func resourceMetricStreamRead(ctx context.Context, d *schema.ResourceData, meta if output.IncludeFilters != nil { if err := d.Set("include_filter", flattenMetricStreamFilters(output.IncludeFilters)); err != nil { - return diag.FromErr(fmt.Errorf("error setting include_filter error: %w", err)) + return diag.Errorf("setting include_filter: %s", err) } } if output.ExcludeFilters != nil { if err := d.Set("exclude_filter", flattenMetricStreamFilters(output.ExcludeFilters)); err != nil { - return diag.FromErr(fmt.Errorf("error setting exclude_filter error: %w", err)) + return diag.Errorf("setting exclude_filter: %s", err) } } if output.StatisticsConfigurations != nil { if err := d.Set("statistics_configuration", flattenMetricStreamStatisticsConfigurations(output.StatisticsConfigurations)); err != nil { - return diag.FromErr(fmt.Errorf("error setting statistics_configuration error: %w", err)) + return diag.Errorf("setting statistics_configuration: %s", err) } } - tags, err := ListTags(conn, aws.StringValue(output.Arn)) + tags, err := ListTagsWithContext(ctx, conn, aws.StringValue(output.Arn)) // Some partitions (i.e., ISO) may not support tagging, giving error if verify.ErrorISOUnsupported(conn.PartitionID, err) { @@ -293,41 +292,165 @@ func resourceMetricStreamRead(ctx context.Context, d *schema.ResourceData, meta } if err != nil { - return diag.Errorf("failed listing tags for CloudWatch Metric Stream (%s): %s", d.Id(), err) + return diag.Errorf("listing tags for CloudWatch Metric Stream (%s): %s", d.Id(), err) } tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return diag.FromErr(fmt.Errorf("error setting tags: %w", err)) + return diag.Errorf("setting tags: %s", err) } if err := d.Set("tags_all", tags.Map()); err != nil { - return diag.FromErr(fmt.Errorf("error setting tags_all: %w", err)) + return diag.Errorf("setting tags_all: %s", err) } return nil } +func resourceMetricStreamUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).CloudWatchConn + + if d.HasChangesExcept("tags", "tags_all") { + input := &cloudwatch.PutMetricStreamInput{ + FirehoseArn: aws.String(d.Get("firehose_arn").(string)), + Name: aws.String(d.Id()), + OutputFormat: aws.String(d.Get("output_format").(string)), + RoleArn: aws.String(d.Get("role_arn").(string)), + } + + if v, ok := d.GetOk("exclude_filter"); ok && v.(*schema.Set).Len() > 0 { + input.ExcludeFilters = expandMetricStreamFilters(v.(*schema.Set)) + } + + if v, ok := d.GetOk("include_filter"); ok && v.(*schema.Set).Len() > 0 { + input.IncludeFilters = expandMetricStreamFilters(v.(*schema.Set)) + } + + if v, ok := d.GetOk("statistics_configuration"); ok && v.(*schema.Set).Len() > 0 { + input.StatisticsConfigurations = expandMetricStreamStatisticsConfigurations(v.(*schema.Set)) + } + + _, err := conn.PutMetricStreamWithContext(ctx, input) + + if err != nil { + return diag.Errorf("updating CloudWatch Metric Stream (%s): %s", d.Id(), err) + } + + if _, err := waitMetricStreamRunning(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("waiting for CloudWatch Metric Stream (%s) update: %s", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + log.Printf("[WARN] failed updating tags for CloudWatch Metric Stream (%s): %s", d.Id(), err) + } + } + + return resourceMetricStreamRead(ctx, d, meta) +} + func resourceMetricStreamDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - log.Printf("[INFO] Deleting CloudWatch Metric Stream %s", d.Id()) conn := meta.(*conns.AWSClient).CloudWatchConn - params := cloudwatch.DeleteMetricStreamInput{ + + log.Printf("[INFO] Deleting CloudWatch Metric Stream: %s", d.Id()) + _, err := conn.DeleteMetricStreamWithContext(ctx, &cloudwatch.DeleteMetricStreamInput{ Name: aws.String(d.Id()), + }) + + if err != nil { + return diag.Errorf("deleting CloudWatch Metric Stream (%s): %s", d.Id(), err) } - if _, err := conn.DeleteMetricStreamWithContext(ctx, ¶ms); err != nil { - return diag.FromErr(fmt.Errorf("error deleting CloudWatch Metric Stream: %s", err)) + if _, err := waitMetricStreamDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("waiting for CloudWatch Metric Stream (%s) delete: %s", d.Id(), err) } - if _, err := WaitMetricStreamDeleted(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)) + return nil +} + +func FindMetricStreamByName(ctx context.Context, conn *cloudwatch.CloudWatch, name string) (*cloudwatch.GetMetricStreamOutput, error) { + input := &cloudwatch.GetMetricStreamInput{ + Name: aws.String(name), } - log.Printf("[INFO] CloudWatch Metric Stream %s deleted", d.Id()) + output, err := conn.GetMetricStreamWithContext(ctx, input) - return nil + if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func statusMetricStream(ctx context.Context, conn *cloudwatch.CloudWatch, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindMetricStreamByName(ctx, conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil + } +} + +const ( + metricStreamStateRunning = "running" + metricStreamStateStopped = "stopped" +) + +func waitMetricStreamDeleted(ctx context.Context, conn *cloudwatch.CloudWatch, name string, timeout time.Duration) (*cloudwatch.GetMetricStreamOutput, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{metricStreamStateRunning, metricStreamStateStopped}, + Target: []string{}, + Refresh: statusMetricStream(ctx, conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*cloudwatch.GetMetricStreamOutput); ok { + return output, err + } + + return nil, err +} + +func waitMetricStreamRunning(ctx context.Context, conn *cloudwatch.CloudWatch, name string, timeout time.Duration) (*cloudwatch.GetMetricStreamOutput, error) { //nolint:unparam + stateConf := &resource.StateChangeConf{ + Pending: []string{metricStreamStateStopped}, + Target: []string{metricStreamStateRunning}, + Refresh: statusMetricStream(ctx, conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*cloudwatch.GetMetricStreamOutput); ok { + return output, err + } + + return nil, err } func validateMetricStreamName(v interface{}, k string) (ws []string, errors []error) { @@ -381,20 +504,16 @@ func expandMetricStreamStatisticsConfigurations(s *schema.Set) []*cloudwatch.Met mConfiguration := configurationRaw.(map[string]interface{}) if v, ok := mConfiguration["additional_statistics"].(*schema.Set); ok && v.Len() > 0 { - log.Printf("[DEBUG] CloudWatch Metric Stream StatisticsConfigurations additional_statistics: %#v", v) configuration.AdditionalStatistics = flex.ExpandStringSet(v) } if v, ok := mConfiguration["include_metric"].(*schema.Set); ok && v.Len() > 0 { - log.Printf("[DEBUG] CloudWatch Metric Stream StatisticsConfigurations include_metrics: %#v", v) configuration.IncludeMetrics = expandMetricStreamStatisticsConfigurationsIncludeMetrics(v) } configurations = append(configurations, configuration) } - log.Printf("[DEBUG] statistics_configurations: %#v", configurations) - if len(configurations) > 0 { return configurations } diff --git a/internal/service/cloudwatch/metric_stream_test.go b/internal/service/cloudwatch/metric_stream_test.go index 1dc984afffb..6651aeeca3f 100644 --- a/internal/service/cloudwatch/metric_stream_test.go +++ b/internal/service/cloudwatch/metric_stream_test.go @@ -1,20 +1,19 @@ package cloudwatch_test import ( + "context" "fmt" "regexp" - "strings" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatch" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "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" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfcloudwatch "github.com/hashicorp/terraform-provider-aws/internal/service/cloudwatch" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func init() { @@ -39,15 +38,20 @@ func TestAccCloudWatchMetricStream_basic(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccMetricStreamConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckMetricStreamExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttr(resourceName, "output_format", "json"), - resource.TestCheckResourceAttr(resourceName, "state", tfcloudwatch.StateRunning), - resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.metric_stream_to_firehose", "arn"), acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "cloudwatch", fmt.Sprintf("metric-stream/%s", rName)), acctest.CheckResourceAttrRFC3339(resourceName, "creation_date"), + resource.TestCheckResourceAttr(resourceName, "exclude_filter.#", "0"), + resource.TestCheckResourceAttr(resourceName, "include_filter.#", "0"), acctest.CheckResourceAttrRFC3339(resourceName, "last_update_date"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "name_prefix", ""), + resource.TestCheckResourceAttr(resourceName, "output_format", "json"), + resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.metric_stream_to_firehose", "arn"), + resource.TestCheckResourceAttrSet(resourceName, "state"), + resource.TestCheckResourceAttr(resourceName, "statistics_configuration.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { @@ -59,8 +63,9 @@ func TestAccCloudWatchMetricStream_basic(t *testing.T) { }) } -func TestAccCloudWatchMetricStream_noName(t *testing.T) { +func TestAccCloudWatchMetricStream_disappears(t *testing.T) { resourceName := "aws_cloudwatch_metric_stream.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -69,9 +74,33 @@ func TestAccCloudWatchMetricStream_noName(t *testing.T) { CheckDestroy: testAccCheckMetricStreamDestroy, Steps: []resource.TestStep{ { - Config: testAccMetricStreamConfig_noName(), + Config: testAccMetricStreamConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckMetricStreamExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfcloudwatch.ResourceMetricStream(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccCloudWatchMetricStream_nameGenerated(t *testing.T) { + resourceName := "aws_cloudwatch_metric_stream.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckMetricStreamDestroy, + Steps: []resource.TestStep{ + { + Config: testAccMetricStreamConfig_nameGenerated(rName), Check: resource.ComposeTestCheckFunc( testAccCheckMetricStreamExists(resourceName), + acctest.CheckResourceAttrNameGenerated(resourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", resource.UniqueIdPrefix), ), }, { @@ -94,10 +123,11 @@ func TestAccCloudWatchMetricStream_namePrefix(t *testing.T) { CheckDestroy: testAccCheckMetricStreamDestroy, Steps: []resource.TestStep{ { - Config: testAccMetricStreamConfig_namePrefix(rName), + Config: testAccMetricStreamConfig_namePrefix(rName, "tf-acc-test-prefix-"), Check: resource.ComposeTestCheckFunc( testAccCheckMetricStreamExists(resourceName), - testAccCheckMetricStreamGeneratedNamePrefix(resourceName, acctest.ResourcePrefix), + acctest.CheckResourceAttrNameFromPrefix(resourceName, "name", "tf-acc-test-prefix-"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "tf-acc-test-prefix-"), ), }, { @@ -200,10 +230,9 @@ func TestAccCloudWatchMetricStream_update(t *testing.T) { }) } -func TestAccCloudWatchMetricStream_updateName(t *testing.T) { +func TestAccCloudWatchMetricStream_tags(t *testing.T) { resourceName := "aws_cloudwatch_metric_stream.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -212,45 +241,34 @@ func TestAccCloudWatchMetricStream_updateName(t *testing.T) { CheckDestroy: testAccCheckMetricStreamDestroy, Steps: []resource.TestStep{ { - Config: testAccMetricStreamConfig_basic(rName), + Config: testAccMetricStreamConfig_tags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckMetricStreamExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, { - Config: testAccMetricStreamConfig_basic(rName2), - Check: resource.ComposeTestCheckFunc( - testAccCheckMetricStreamExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "name", rName2), - testAccCheckMetricStreamDestroyPrevious(rName), - ), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, - }, - }) -} - -func TestAccCloudWatchMetricStream_tags(t *testing.T) { - resourceName := "aws_cloudwatch_metric_stream.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckMetricStreamDestroy, - Steps: []resource.TestStep{ { - Config: testAccMetricStreamConfig_tags(rName), + Config: testAccMetricStreamConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckMetricStreamExists(resourceName), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + Config: testAccMetricStreamConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckMetricStreamExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), }, }, }) @@ -327,36 +345,19 @@ func TestAccCloudWatchMetricStream_additional_statistics(t *testing.T) { }) } -func testAccCheckMetricStreamGeneratedNamePrefix(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 testAccCheckMetricStreamExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } + if rs.Primary.ID == "" { + return fmt.Errorf("No CloudWatch Metric Stream ID is set") + } conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchConn - params := cloudwatch.GetMetricStreamInput{ - Name: aws.String(rs.Primary.ID), - } - _, err := conn.GetMetricStream(¶ms) + _, err := tfcloudwatch.FindMetricStreamByName(context.Background(), conn, rs.Primary.ID) return err } @@ -370,55 +371,26 @@ func testAccCheckMetricStreamDestroy(s *terraform.State) error { 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 !tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFoundException) { - return err - } - } - - return nil -} - -func testAccCheckMetricStreamDestroyPrevious(name string) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchConn - - params := cloudwatch.GetMetricStreamInput{ - Name: aws.String(name), - } - - _, err := conn.GetMetricStream(¶ms) + _, err := tfcloudwatch.FindMetricStreamByName(context.Background(), conn, rs.Primary.ID) - if err == nil { - return fmt.Errorf("MetricStream still exists: %s", name) + if tfresource.NotFound(err) { + continue } - if !tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFoundException) { + if err != nil { return err } - return nil + return fmt.Errorf("CloudWatch Metric Stream %s still exists", rs.Primary.ID) } + + return nil } -func testAccMetricStreamConfig_basic(rName string) string { +func testAccMetricStreamConfig_base(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} -resource "aws_cloudwatch_metric_stream" "test" { - 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" -} - resource "aws_iam_role" "metric_stream_to_firehose" { name = %[1]q @@ -504,10 +476,10 @@ resource "aws_iam_role_policy" "firehose_to_s3" { "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:PutObject" - ], - "Resource": [ + ], + "Resource": [ "${aws_s3_bucket.bucket.arn}", - "${aws_s3_bucket.bucket.arn}/*" + "${aws_s3_bucket.bucket.arn}/*" ] } ] @@ -527,6 +499,38 @@ resource "aws_kinesis_firehose_delivery_stream" "s3_stream" { `, rName) } +func testAccMetricStreamConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccMetricStreamConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudwatch_metric_stream" "test" { + 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" +} +`, rName)) +} + +func testAccMetricStreamConfig_nameGenerated(rName string) string { + return acctest.ConfigCompose(testAccMetricStreamConfig_base(rName), ` +resource "aws_cloudwatch_metric_stream" "test" { + role_arn = aws_iam_role.metric_stream_to_firehose.arn + firehose_arn = aws_kinesis_firehose_delivery_stream.s3_stream.arn + output_format = "json" +} +`) +} + +func testAccMetricStreamConfig_namePrefix(rName, namePrefix string) string { + return acctest.ConfigCompose(testAccMetricStreamConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudwatch_metric_stream" "test" { + name_prefix = %[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" +} +`, namePrefix)) +} + func testAccMetricStreamConfig_updateARN(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} @@ -565,35 +569,6 @@ resource "aws_cloudwatch_metric_stream" "test" { `, rName) } -func testAccMetricStreamConfig_noName() string { - return ` -data "aws_partition" "current" {} -data "aws_region" "current" {} -data "aws_caller_identity" "current" {} - -resource "aws_cloudwatch_metric_stream" "test" { - 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" -} -` -} - -func testAccMetricStreamConfig_namePrefix(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 = %[1]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" -} -`, rName) -} - func testAccMetricStreamConfig_excludeFilters(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} @@ -617,24 +592,35 @@ resource "aws_cloudwatch_metric_stream" "test" { `, rName) } -func testAccMetricStreamConfig_tags(rName string) string { - return fmt.Sprintf(` -data "aws_partition" "current" {} -data "aws_region" "current" {} -data "aws_caller_identity" "current" {} +func testAccMetricStreamConfig_tags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccMetricStreamConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudwatch_metric_stream" "test" { + 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" + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1)) +} + +func testAccMetricStreamConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccMetricStreamConfig_base(rName), fmt.Sprintf(` resource "aws_cloudwatch_metric_stream" "test" { name = %[1]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" + role_arn = aws_iam_role.metric_stream_to_firehose.arn + firehose_arn = aws_kinesis_firehose_delivery_stream.s3_stream.arn output_format = "json" tags = { - Name = %[1]q - Mercedes = "Toto" + %[2]q = %[3]q + %[4]q = %[5]q } } -`, rName) +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) } func testAccMetricStreamConfig_additionalStatistics(rName string, stat string) string { diff --git a/internal/service/cloudwatch/status.go b/internal/service/cloudwatch/status.go deleted file mode 100644 index cd85a66d363..00000000000 --- a/internal/service/cloudwatch/status.go +++ /dev/null @@ -1,28 +0,0 @@ -package cloudwatch - -import ( - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/cloudwatch" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func StatusMetricStreamState(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/internal/service/cloudwatch/wait.go b/internal/service/cloudwatch/wait.go deleted file mode 100644 index 1c06be9f498..00000000000 --- a/internal/service/cloudwatch/wait.go +++ /dev/null @@ -1,58 +0,0 @@ -package cloudwatch - -import ( - "context" - "time" - - "github.com/aws/aws-sdk-go/service/cloudwatch" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -const ( - MetricStreamDeleteTimeout = 2 * time.Minute - MetricStreamReadyTimeout = 1 * time.Minute - - StateRunning = "running" - StateStopped = "stopped" -) - -func WaitMetricStreamDeleted(ctx context.Context, conn *cloudwatch.CloudWatch, name string) (*cloudwatch.GetMetricStreamOutput, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{ - StateRunning, - StateStopped, - }, - Target: []string{}, - Refresh: StatusMetricStreamState(ctx, conn, name), - Timeout: MetricStreamDeleteTimeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if v, ok := outputRaw.(*cloudwatch.GetMetricStreamOutput); ok { - return v, err - } - - return nil, err -} - -func WaitMetricStreamReady(ctx context.Context, conn *cloudwatch.CloudWatch, name string) (*cloudwatch.GetMetricStreamOutput, error) { - stateConf := &resource.StateChangeConf{ - Pending: []string{ - StateStopped, - }, - Target: []string{ - StateRunning, - }, - Refresh: StatusMetricStreamState(ctx, conn, name), - Timeout: MetricStreamReadyTimeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if v, ok := outputRaw.(*cloudwatch.GetMetricStreamOutput); ok { - return v, err - } - - return nil, err -}