From 3238e2b7626fdbffcba186bd8a2987ee58a07132 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 30 Jan 2024 08:59:10 -0500 Subject: [PATCH 01/12] cloudwatch: Use AWS SDK for Go v2. --- names/data/names_data.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/names/data/names_data.csv b/names/data/names_data.csv index 9bd4d8afe2c..63017705020 100644 --- a/names/data/names_data.csv +++ b/names/data/names_data.csv @@ -67,7 +67,7 @@ cloudsearch,cloudsearch,cloudsearch,cloudsearch,,cloudsearch,,,CloudSearch,Cloud cloudsearchdomain,cloudsearchdomain,cloudsearchdomain,cloudsearchdomain,,cloudsearchdomain,,,CloudSearchDomain,CloudSearchDomain,,1,,,aws_cloudsearchdomain_,,cloudsearchdomain_,CloudSearch Domain,Amazon,,x,,,,,CloudSearch Domain,,, ,,,,,,,,,,,,,,,,,CloudShell,AWS,x,,,,,,,,,No SDK support cloudtrail,cloudtrail,cloudtrail,cloudtrail,,cloudtrail,,,CloudTrail,CloudTrail,,1,,aws_cloudtrail,aws_cloudtrail_,,cloudtrail,CloudTrail,AWS,,,,,,,CloudTrail,,, -cloudwatch,cloudwatch,cloudwatch,cloudwatch,,cloudwatch,,,CloudWatch,CloudWatch,,1,,aws_cloudwatch_(?!(event_|log_|query_)),aws_cloudwatch_,,cloudwatch_dashboard;cloudwatch_metric_;cloudwatch_composite_,CloudWatch,Amazon,,,,,,,CloudWatch,,, +cloudwatch,cloudwatch,cloudwatch,cloudwatch,,cloudwatch,,,CloudWatch,CloudWatch,,,2,aws_cloudwatch_(?!(event_|log_|query_)),aws_cloudwatch_,,cloudwatch_dashboard;cloudwatch_metric_;cloudwatch_composite_,CloudWatch,Amazon,,,,,,,CloudWatch,,, application-insights,applicationinsights,applicationinsights,applicationinsights,,applicationinsights,,,ApplicationInsights,ApplicationInsights,,1,,,aws_applicationinsights_,,applicationinsights_,CloudWatch Application Insights,Amazon,,,,,,,Application Insights,,, evidently,evidently,cloudwatchevidently,evidently,,evidently,,cloudwatchevidently,Evidently,CloudWatchEvidently,,,2,,aws_evidently_,,evidently_,CloudWatch Evidently,Amazon,,,,,,,Evidently,,, internetmonitor,internetmonitor,internetmonitor,internetmonitor,,internetmonitor,,,InternetMonitor,InternetMonitor,,,2,,aws_internetmonitor_,,internetmonitor_,CloudWatch Internet Monitor,Amazon,,,,,,,InternetMonitor,ListMonitors,, From f22ae371b73d840ffc30d2f86abf1058d9bb6f7f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 30 Jan 2024 09:00:55 -0500 Subject: [PATCH 02/12] Run 'make gen'. --- internal/conns/awsclient_gen.go | 6 +-- internal/service/cloudwatch/generate.go | 2 +- .../service/cloudwatch/service_package_gen.go | 17 +++++---- internal/service/cloudwatch/tags_gen.go | 38 +++++++++---------- 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/internal/conns/awsclient_gen.go b/internal/conns/awsclient_gen.go index 11021fa9c9e..add2195a829 100644 --- a/internal/conns/awsclient_gen.go +++ b/internal/conns/awsclient_gen.go @@ -20,6 +20,7 @@ import ( chimesdkvoice_sdkv2 "github.com/aws/aws-sdk-go-v2/service/chimesdkvoice" cleanrooms_sdkv2 "github.com/aws/aws-sdk-go-v2/service/cleanrooms" cloudcontrol_sdkv2 "github.com/aws/aws-sdk-go-v2/service/cloudcontrol" + cloudwatch_sdkv2 "github.com/aws/aws-sdk-go-v2/service/cloudwatch" cloudwatchlogs_sdkv2 "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" codeartifact_sdkv2 "github.com/aws/aws-sdk-go-v2/service/codeartifact" codebuild_sdkv2 "github.com/aws/aws-sdk-go-v2/service/codebuild" @@ -137,7 +138,6 @@ import ( cloudhsmv2_sdkv1 "github.com/aws/aws-sdk-go/service/cloudhsmv2" cloudsearch_sdkv1 "github.com/aws/aws-sdk-go/service/cloudsearch" cloudtrail_sdkv1 "github.com/aws/aws-sdk-go/service/cloudtrail" - cloudwatch_sdkv1 "github.com/aws/aws-sdk-go/service/cloudwatch" cloudwatchrum_sdkv1 "github.com/aws/aws-sdk-go/service/cloudwatchrum" cognitoidentity_sdkv1 "github.com/aws/aws-sdk-go/service/cognitoidentity" cognitoidentityprovider_sdkv1 "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" @@ -400,8 +400,8 @@ func (c *AWSClient) CloudTrailConn(ctx context.Context) *cloudtrail_sdkv1.CloudT return errs.Must(conn[*cloudtrail_sdkv1.CloudTrail](ctx, c, names.CloudTrail, make(map[string]any))) } -func (c *AWSClient) CloudWatchConn(ctx context.Context) *cloudwatch_sdkv1.CloudWatch { - return errs.Must(conn[*cloudwatch_sdkv1.CloudWatch](ctx, c, names.CloudWatch, make(map[string]any))) +func (c *AWSClient) CloudWatchClient(ctx context.Context) *cloudwatch_sdkv2.Client { + return errs.Must(client[*cloudwatch_sdkv2.Client](ctx, c, names.CloudWatch, make(map[string]any))) } func (c *AWSClient) CodeArtifactClient(ctx context.Context) *codeartifact_sdkv2.Client { diff --git a/internal/service/cloudwatch/generate.go b/internal/service/cloudwatch/generate.go index 5e45b181003..b31ac288a35 100644 --- a/internal/service/cloudwatch/generate.go +++ b/internal/service/cloudwatch/generate.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -//go:generate go run ../../generate/tags/main.go -ListTags -ListTagsInIDElem=ResourceARN -ServiceTagsSlice -TagInIDElem=ResourceARN -UpdateTags -CreateTags +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -ListTags -ListTagsInIDElem=ResourceARN -ServiceTagsSlice -TagInIDElem=ResourceARN -UpdateTags -CreateTags //go:generate go run ../../generate/servicepackage/main.go // ONLY generate directives and package declaration! Do not add anything else to this file. diff --git a/internal/service/cloudwatch/service_package_gen.go b/internal/service/cloudwatch/service_package_gen.go index 608f2fb673a..800a8270db5 100644 --- a/internal/service/cloudwatch/service_package_gen.go +++ b/internal/service/cloudwatch/service_package_gen.go @@ -5,9 +5,8 @@ package cloudwatch import ( "context" - aws_sdkv1 "github.com/aws/aws-sdk-go/aws" - session_sdkv1 "github.com/aws/aws-sdk-go/aws/session" - cloudwatch_sdkv1 "github.com/aws/aws-sdk-go/service/cloudwatch" + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + cloudwatch_sdkv2 "github.com/aws/aws-sdk-go-v2/service/cloudwatch" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/types" "github.com/hashicorp/terraform-provider-aws/names" @@ -64,11 +63,15 @@ func (p *servicePackage) ServicePackageName() string { return names.CloudWatch } -// NewConn returns a new AWS SDK for Go v1 client for this service package's AWS API. -func (p *servicePackage) NewConn(ctx context.Context, config map[string]any) (*cloudwatch_sdkv1.CloudWatch, error) { - sess := config["session"].(*session_sdkv1.Session) +// NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API. +func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*cloudwatch_sdkv2.Client, error) { + cfg := *(config["aws_sdkv2_config"].(*aws_sdkv2.Config)) - return cloudwatch_sdkv1.New(sess.Copy(&aws_sdkv1.Config{Endpoint: aws_sdkv1.String(config["endpoint"].(string))})), nil + return cloudwatch_sdkv2.NewFromConfig(cfg, func(o *cloudwatch_sdkv2.Options) { + if endpoint := config["endpoint"].(string); endpoint != "" { + o.BaseEndpoint = aws_sdkv2.String(endpoint) + } + }), nil } func ServicePackage(ctx context.Context) conns.ServicePackage { diff --git a/internal/service/cloudwatch/tags_gen.go b/internal/service/cloudwatch/tags_gen.go index b99205cb66a..99a3e883a67 100644 --- a/internal/service/cloudwatch/tags_gen.go +++ b/internal/service/cloudwatch/tags_gen.go @@ -5,9 +5,9 @@ import ( "context" "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/cloudwatch" - "github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/logging" @@ -19,12 +19,12 @@ import ( // listTags lists cloudwatch service tags. // The identifier is typically the Amazon Resource Name (ARN), although // it may also be a different identifier depending on the service. -func listTags(ctx context.Context, conn cloudwatchiface.CloudWatchAPI, identifier string) (tftags.KeyValueTags, error) { +func listTags(ctx context.Context, conn *cloudwatch.Client, identifier string, optFns ...func(*cloudwatch.Options)) (tftags.KeyValueTags, error) { input := &cloudwatch.ListTagsForResourceInput{ ResourceARN: aws.String(identifier), } - output, err := conn.ListTagsForResourceWithContext(ctx, input) + output, err := conn.ListTagsForResource(ctx, input, optFns...) if err != nil { return tftags.New(ctx, nil), err @@ -36,7 +36,7 @@ func listTags(ctx context.Context, conn cloudwatchiface.CloudWatchAPI, identifie // ListTags lists cloudwatch service tags and set them in Context. // It is called from outside this package. func (p *servicePackage) ListTags(ctx context.Context, meta any, identifier string) error { - tags, err := listTags(ctx, meta.(*conns.AWSClient).CloudWatchConn(ctx), identifier) + tags, err := listTags(ctx, meta.(*conns.AWSClient).CloudWatchClient(ctx), identifier) if err != nil { return err @@ -52,11 +52,11 @@ func (p *servicePackage) ListTags(ctx context.Context, meta any, identifier stri // []*SERVICE.Tag handling // Tags returns cloudwatch service tags. -func Tags(tags tftags.KeyValueTags) []*cloudwatch.Tag { - result := make([]*cloudwatch.Tag, 0, len(tags)) +func Tags(tags tftags.KeyValueTags) []awstypes.Tag { + result := make([]awstypes.Tag, 0, len(tags)) for k, v := range tags.Map() { - tag := &cloudwatch.Tag{ + tag := awstypes.Tag{ Key: aws.String(k), Value: aws.String(v), } @@ -68,11 +68,11 @@ func Tags(tags tftags.KeyValueTags) []*cloudwatch.Tag { } // KeyValueTags creates tftags.KeyValueTags from cloudwatch service tags. -func KeyValueTags(ctx context.Context, tags []*cloudwatch.Tag) tftags.KeyValueTags { +func KeyValueTags(ctx context.Context, tags []awstypes.Tag) tftags.KeyValueTags { m := make(map[string]*string, len(tags)) for _, tag := range tags { - m[aws.StringValue(tag.Key)] = tag.Value + m[aws.ToString(tag.Key)] = tag.Value } return tftags.New(ctx, m) @@ -80,7 +80,7 @@ func KeyValueTags(ctx context.Context, tags []*cloudwatch.Tag) tftags.KeyValueTa // getTagsIn returns cloudwatch service tags from Context. // nil is returned if there are no input tags. -func getTagsIn(ctx context.Context) []*cloudwatch.Tag { +func getTagsIn(ctx context.Context) []awstypes.Tag { if inContext, ok := tftags.FromContext(ctx); ok { if tags := Tags(inContext.TagsIn.UnwrapOrDefault()); len(tags) > 0 { return tags @@ -91,14 +91,14 @@ func getTagsIn(ctx context.Context) []*cloudwatch.Tag { } // setTagsOut sets cloudwatch service tags in Context. -func setTagsOut(ctx context.Context, tags []*cloudwatch.Tag) { +func setTagsOut(ctx context.Context, tags []awstypes.Tag) { if inContext, ok := tftags.FromContext(ctx); ok { inContext.TagsOut = option.Some(KeyValueTags(ctx, tags)) } } // createTags creates cloudwatch service tags for new resources. -func createTags(ctx context.Context, conn cloudwatchiface.CloudWatchAPI, identifier string, tags []*cloudwatch.Tag) error { +func createTags(ctx context.Context, conn *cloudwatch.Client, identifier string, tags []awstypes.Tag) error { if len(tags) == 0 { return nil } @@ -109,7 +109,7 @@ func createTags(ctx context.Context, conn cloudwatchiface.CloudWatchAPI, identif // updateTags updates cloudwatch service tags. // The identifier is typically the Amazon Resource Name (ARN), although // it may also be a different identifier depending on the service. -func updateTags(ctx context.Context, conn cloudwatchiface.CloudWatchAPI, identifier string, oldTagsMap, newTagsMap any) error { +func updateTags(ctx context.Context, conn *cloudwatch.Client, identifier string, oldTagsMap, newTagsMap any, optFns ...func(*cloudwatch.Options)) error { oldTags := tftags.New(ctx, oldTagsMap) newTags := tftags.New(ctx, newTagsMap) @@ -120,10 +120,10 @@ func updateTags(ctx context.Context, conn cloudwatchiface.CloudWatchAPI, identif if len(removedTags) > 0 { input := &cloudwatch.UntagResourceInput{ ResourceARN: aws.String(identifier), - TagKeys: aws.StringSlice(removedTags.Keys()), + TagKeys: removedTags.Keys(), } - _, err := conn.UntagResourceWithContext(ctx, input) + _, err := conn.UntagResource(ctx, input, optFns...) if err != nil { return fmt.Errorf("untagging resource (%s): %w", identifier, err) @@ -138,7 +138,7 @@ func updateTags(ctx context.Context, conn cloudwatchiface.CloudWatchAPI, identif Tags: Tags(updatedTags), } - _, err := conn.TagResourceWithContext(ctx, input) + _, err := conn.TagResource(ctx, input, optFns...) if err != nil { return fmt.Errorf("tagging resource (%s): %w", identifier, err) @@ -151,5 +151,5 @@ func updateTags(ctx context.Context, conn cloudwatchiface.CloudWatchAPI, identif // UpdateTags updates cloudwatch service tags. // It is called from outside this package. func (p *servicePackage) UpdateTags(ctx context.Context, meta any, identifier string, oldTags, newTags any) error { - return updateTags(ctx, meta.(*conns.AWSClient).CloudWatchConn(ctx), identifier, oldTags, newTags) + return updateTags(ctx, meta.(*conns.AWSClient).CloudWatchClient(ctx), identifier, oldTags, newTags) } From 481c910d691e162eccde5224292af67d934d3d10 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 30 Jan 2024 10:42:34 -0500 Subject: [PATCH 03/12] run 'go get github.com/aws/aws-sdk-go-v2/service/cloudwatch@v1.32.2 && go mod tidy'. --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 8682d51df66..1d54beca82e 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/chimesdkvoice v1.12.6 github.com/aws/aws-sdk-go-v2/service/cleanrooms v1.8.6 github.com/aws/aws-sdk-go-v2/service/cloudcontrol v1.15.7 + github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.32.2 github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.31.0 github.com/aws/aws-sdk-go-v2/service/codeartifact v1.23.6 github.com/aws/aws-sdk-go-v2/service/codebuild v1.28.0 diff --git a/go.sum b/go.sum index 0871043f946..be90d61f2d4 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,8 @@ github.com/aws/aws-sdk-go-v2/service/cleanrooms v1.8.6 h1:ype6mmLnDjOX8d4pkbj7SX github.com/aws/aws-sdk-go-v2/service/cleanrooms v1.8.6/go.mod h1:ibuCTolZ5/w65nBDKpsXhzZUeQluX/m0hnXAiwFPvP8= github.com/aws/aws-sdk-go-v2/service/cloudcontrol v1.15.7 h1:8sBfx7QkDZ6dgfUNXWHWRc6Eax7WOI3Slgj6OKDHKTI= github.com/aws/aws-sdk-go-v2/service/cloudcontrol v1.15.7/go.mod h1:P1EMD13hrBE2KUw030w482Eyk2NmOFIvGqmgNi4XRDc= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.32.2 h1:vQfCIHSDouEvbE4EuDrlCGKcrtABEqF3cMt61nGEV4g= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.32.2/go.mod h1:3ToKMEhVj+Q+HzZ8Hqin6LdAKtsi3zVXVNUPpQMd+Xk= github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.31.0 h1:Rk+Ft0Mu/eiNt2iJ2oS8Gf1h5m6q5crwS8cmlTylnvM= github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.31.0/go.mod h1:jZNaJEtn9TLi3pfxycLz79HVkKxP8ZdYm92iaNFgBsA= github.com/aws/aws-sdk-go-v2/service/codeartifact v1.23.6 h1:QuI+Nh9lQ9EgCMhLzIDEp95cLnNd1vFHyODD0he0oQs= From ccfa0ff7e768823f31d4280b2367355b29614156 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 30 Jan 2024 11:00:33 -0500 Subject: [PATCH 04/12] r/aws_cloudwatch_dashboard: Migrate to AWS SDK for Go v2. --- internal/service/cloudwatch/dashboard.go | 125 ++++++++++-------- internal/service/cloudwatch/exports_test.go | 11 ++ .../service/cloudwatch/service_package_gen.go | 3 +- 3 files changed, 84 insertions(+), 55 deletions(-) create mode 100644 internal/service/cloudwatch/exports_test.go diff --git a/internal/service/cloudwatch/dashboard.go b/internal/service/cloudwatch/dashboard.go index d408802fb77..8fa5e32fdfa 100644 --- a/internal/service/cloudwatch/dashboard.go +++ b/internal/service/cloudwatch/dashboard.go @@ -7,22 +7,24 @@ import ( "context" "log" - "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/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "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/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" - "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_cloudwatch_dashboard") -func ResourceDashboard() *schema.Resource { +// @SDKResource("aws_cloudwatch_dashboard", name="Dashboard") +func resourceDashboard() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceDashboardPut, ReadWithoutTimeout: resourceDashboardRead, @@ -43,14 +45,15 @@ func ResourceDashboard() *schema.Resource { Computed: true, }, "dashboard_body": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsJSON, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsJSON, + DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, + DiffSuppressOnRefresh: true, StateFunc: func(v interface{}) string { json, _ := structure.NormalizeJsonString(v) return json }, - DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, }, "dashboard_name": { Type: schema.TypeString, @@ -62,75 +65,89 @@ func ResourceDashboard() *schema.Resource { } } -func resourceDashboardRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceDashboardPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - dashboardName := d.Get("dashboard_name").(string) - log.Printf("[DEBUG] Reading CloudWatch Dashboard: %s", dashboardName) - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) - params := cloudwatch.GetDashboardInput{ - DashboardName: aws.String(d.Id()), + name := d.Get("dashboard_name").(string) + input := &cloudwatch.PutDashboardInput{ + DashboardBody: aws.String(d.Get("dashboard_body").(string)), + DashboardName: aws.String(name), + } + + _, err := conn.PutDashboard(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "putting CloudWatch Dashboard (%s): %s", name, err) } - resp, err := conn.GetDashboardWithContext(ctx, ¶ms) - if !d.IsNewResource() && IsDashboardNotFoundErr(err) { - create.LogNotFoundRemoveState(names.CloudWatch, create.ErrActionReading, ResNameDashboard, d.Id()) + if d.IsNewResource() { + d.SetId(name) + } + + return append(diags, resourceDashboardRead(ctx, d, meta)...) +} + +func resourceDashboardRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) + + output, err := findDashboardByName(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] CloudWatch Dashboard (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.CloudWatch, create.ErrActionReading, ResNameDashboard, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading CloudWatch Dashboard (%s): %s", d.Id(), err) } - d.Set("dashboard_arn", resp.DashboardArn) - d.Set("dashboard_name", resp.DashboardName) - d.Set("dashboard_body", resp.DashboardBody) + d.Set("dashboard_arn", output.DashboardArn) + d.Set("dashboard_body", output.DashboardBody) + d.Set("dashboard_name", output.DashboardName) + return diags } -func resourceDashboardPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceDashboardDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) - params := cloudwatch.PutDashboardInput{ - DashboardBody: aws.String(d.Get("dashboard_body").(string)), - DashboardName: aws.String(d.Get("dashboard_name").(string)), - } + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) - log.Printf("[DEBUG] Putting CloudWatch Dashboard: %#v", params) + log.Printf("[DEBUG] Deleting CloudWatch Dashboard: %s", d.Id()) + _, err := conn.DeleteDashboards(ctx, &cloudwatch.DeleteDashboardsInput{ + DashboardNames: tfslices.Of(d.Id()), + }) - _, err := conn.PutDashboardWithContext(ctx, ¶ms) if err != nil { - return sdkdiag.AppendErrorf(diags, "Putting dashboard failed: %s", err) + return sdkdiag.AppendErrorf(diags, "deleting CloudWatch Dashboard (%s): %s", d.Id(), err) } - d.SetId(d.Get("dashboard_name").(string)) - log.Println("[INFO] CloudWatch Dashboard put finished") - return append(diags, resourceDashboardRead(ctx, d, meta)...) + return diags } -func resourceDashboardDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - log.Printf("[INFO] Deleting CloudWatch Dashboard %s", d.Id()) - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) - params := cloudwatch.DeleteDashboardsInput{ - DashboardNames: []*string{aws.String(d.Id())}, +func findDashboardByName(ctx context.Context, conn *cloudwatch.Client, name string) (*cloudwatch.GetDashboardOutput, error) { + input := &cloudwatch.GetDashboardInput{ + DashboardName: aws.String(name), } - if _, err := conn.DeleteDashboardsWithContext(ctx, ¶ms); err != nil { - if IsDashboardNotFoundErr(err) { - return diags + output, err := conn.GetDashboard(ctx, input) + + if errs.IsA[*types.ResourceNotFound](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, } - return sdkdiag.AppendErrorf(diags, "deleting CloudWatch Dashboard: %s", err) } - log.Printf("[INFO] CloudWatch Dashboard %s deleted", d.Id()) - return diags -} + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } -func IsDashboardNotFoundErr(err error) bool { - return tfawserr.ErrMessageContains( - err, - "ResourceNotFound", - "does not exist") + return output, nil } diff --git a/internal/service/cloudwatch/exports_test.go b/internal/service/cloudwatch/exports_test.go new file mode 100644 index 00000000000..c93d42d5ab0 --- /dev/null +++ b/internal/service/cloudwatch/exports_test.go @@ -0,0 +1,11 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cloudwatch + +// Exports for use in tests only. +var ( + ResourceDashboard = resourceDashboard + + FindDashboardByName = findDashboardByName +) diff --git a/internal/service/cloudwatch/service_package_gen.go b/internal/service/cloudwatch/service_package_gen.go index 800a8270db5..1d2e0b4754f 100644 --- a/internal/service/cloudwatch/service_package_gen.go +++ b/internal/service/cloudwatch/service_package_gen.go @@ -37,8 +37,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceDashboard, + Factory: resourceDashboard, TypeName: "aws_cloudwatch_dashboard", + Name: "Dashboard", }, { Factory: ResourceMetricAlarm, From b8580429ca6e23178b091da76bd2af0c171117ec Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 30 Jan 2024 11:53:13 -0500 Subject: [PATCH 05/12] r/aws_cloudwatch_metric_stream: Migrate to AWS SDK for Go v2. --- .changelog/#####.txt | 3 + internal/service/cloudwatch/exports_test.go | 6 +- internal/service/cloudwatch/metric_stream.go | 275 ++++++++++-------- .../service/cloudwatch/service_package_gen.go | 4 +- 4 files changed, 164 insertions(+), 124 deletions(-) create mode 100644 .changelog/#####.txt diff --git a/.changelog/#####.txt b/.changelog/#####.txt new file mode 100644 index 00000000000..32e817bcd05 --- /dev/null +++ b/.changelog/#####.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_cloudwatch_metric_stream: Add plan-time validation of `output_format` +``` \ No newline at end of file diff --git a/internal/service/cloudwatch/exports_test.go b/internal/service/cloudwatch/exports_test.go index c93d42d5ab0..4e499214fe6 100644 --- a/internal/service/cloudwatch/exports_test.go +++ b/internal/service/cloudwatch/exports_test.go @@ -5,7 +5,9 @@ package cloudwatch // Exports for use in tests only. var ( - ResourceDashboard = resourceDashboard + ResourceDashboard = resourceDashboard + ResourceMetricStream = resourceMetricStream - FindDashboardByName = findDashboardByName + FindDashboardByName = findDashboardByName + FindMetricStreamByName = findMetricStreamByName ) diff --git a/internal/service/cloudwatch/metric_stream.go b/internal/service/cloudwatch/metric_stream.go index c26b2f142ee..ce0d998ce3d 100644 --- a/internal/service/cloudwatch/metric_stream.go +++ b/internal/service/cloudwatch/metric_stream.go @@ -9,15 +9,16 @@ import ( "time" "github.com/YakDriver/regexache" - "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/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "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/enum" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" @@ -27,9 +28,9 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_cloudwatch_metric_stream", name="Metric Alarm") +// @SDKResource("aws_cloudwatch_metric_stream", name="Metric Stream") // @Tags(identifierAttribute="arn") -func ResourceMetricStream() *schema.Resource { +func resourceMetricStream() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceMetricStreamCreate, ReadWithoutTimeout: resourceMetricStreamRead, @@ -131,9 +132,9 @@ func ResourceMetricStream() *schema.Resource { ValidateFunc: validateMetricStreamName, }, "output_format": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringLenBetween(1, 255), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.MetricStreamOutputFormat](), }, "role_arn": { Type: schema.TypeString, @@ -201,38 +202,37 @@ func ResourceMetricStream() *schema.Resource { func resourceMetricStreamCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) name := create.Name(d.Get("name").(string), d.Get("name_prefix").(string)) input := &cloudwatch.PutMetricStreamInput{ FirehoseArn: aws.String(d.Get("firehose_arn").(string)), IncludeLinkedAccountsMetrics: aws.Bool(d.Get("include_linked_accounts_metrics").(bool)), Name: aws.String(name), - OutputFormat: aws.String(d.Get("output_format").(string)), + OutputFormat: types.MetricStreamOutputFormat(d.Get("output_format").(string)), RoleArn: aws.String(d.Get("role_arn").(string)), Tags: getTagsIn(ctx), } if v, ok := d.GetOk("exclude_filter"); ok && v.(*schema.Set).Len() > 0 { - input.ExcludeFilters = expandMetricStreamFilters(v.(*schema.Set)) + input.ExcludeFilters = expandMetricStreamFilters(v.(*schema.Set).List()) } if v, ok := d.GetOk("include_filter"); ok && v.(*schema.Set).Len() > 0 { - input.IncludeFilters = expandMetricStreamFilters(v.(*schema.Set)) + input.IncludeFilters = expandMetricStreamFilters(v.(*schema.Set).List()) } if v, ok := d.GetOk("statistics_configuration"); ok && v.(*schema.Set).Len() > 0 { - input.StatisticsConfigurations = expandMetricStreamStatisticsConfigurations(v.(*schema.Set)) + input.StatisticsConfigurations = expandMetricStreamStatisticsConfigurations(v.(*schema.Set).List()) } - output, err := conn.PutMetricStreamWithContext(ctx, input) + output, err := conn.PutMetricStream(ctx, input) // Some partitions (e.g. ISO) may not support tag-on-create. - if input.Tags != nil && errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { + if input.Tags != nil && errs.IsUnsupportedOperationInPartitionError(meta.(*conns.AWSClient).Partition, err) { input.Tags = nil - output, err = conn.PutMetricStreamWithContext(ctx, input) + output, err = conn.PutMetricStream(ctx, input) } if err != nil { @@ -247,10 +247,10 @@ func resourceMetricStreamCreate(ctx context.Context, d *schema.ResourceData, met // For partitions not supporting tag-on-create, attempt tag after create. if tags := getTagsIn(ctx); input.Tags == nil && len(tags) > 0 { - err := createTags(ctx, conn, aws.StringValue(output.Arn), tags) + err := createTags(ctx, conn, aws.ToString(output.Arn), tags) // If default tags only, continue. Otherwise, error. - if v, ok := d.GetOk(names.AttrTags); (!ok || len(v.(map[string]interface{})) == 0) && errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { + if v, ok := d.GetOk(names.AttrTags); (!ok || len(v.(map[string]interface{})) == 0) && errs.IsUnsupportedOperationInPartitionError(meta.(*conns.AWSClient).Partition, err) { return append(diags, resourceMetricStreamRead(ctx, d, meta)...) } @@ -264,10 +264,9 @@ func resourceMetricStreamCreate(ctx context.Context, d *schema.ResourceData, met func resourceMetricStreamRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) - - output, err := FindMetricStreamByName(ctx, conn, d.Id()) + output, err := findMetricStreamByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] CloudWatch Metric Stream (%s) not found, removing from state", d.Id()) @@ -281,27 +280,24 @@ func resourceMetricStreamRead(ctx context.Context, d *schema.ResourceData, meta d.Set("arn", output.Arn) d.Set("creation_date", output.CreationDate.Format(time.RFC3339)) + if output.ExcludeFilters != nil { + if err := d.Set("exclude_filter", flattenMetricStreamFilters(output.ExcludeFilters)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting exclude_filter: %s", err) + } + } d.Set("firehose_arn", output.FirehoseArn) + if output.IncludeFilters != nil { + if err := d.Set("include_filter", flattenMetricStreamFilters(output.IncludeFilters)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting include_filter: %s", err) + } + } d.Set("include_linked_accounts_metrics", output.IncludeLinkedAccountsMetrics) d.Set("last_update_date", output.CreationDate.Format(time.RFC3339)) d.Set("name", output.Name) - d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(output.Name))) + d.Set("name_prefix", create.NamePrefixFromName(aws.ToString(output.Name))) d.Set("output_format", output.OutputFormat) d.Set("role_arn", output.RoleArn) d.Set("state", output.State) - - if output.IncludeFilters != nil { - if err := d.Set("include_filter", flattenMetricStreamFilters(output.IncludeFilters)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting include_filter: %s", err) - } - } - - if output.ExcludeFilters != nil { - if err := d.Set("exclude_filter", flattenMetricStreamFilters(output.ExcludeFilters)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting exclude_filter: %s", err) - } - } - if output.StatisticsConfigurations != nil { if err := d.Set("statistics_configuration", flattenMetricStreamStatisticsConfigurations(output.StatisticsConfigurations)); err != nil { return sdkdiag.AppendErrorf(diags, "setting statistics_configuration: %s", err) @@ -313,31 +309,30 @@ func resourceMetricStreamRead(ctx context.Context, d *schema.ResourceData, meta func resourceMetricStreamUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) if d.HasChangesExcept("tags", "tags_all") { input := &cloudwatch.PutMetricStreamInput{ FirehoseArn: aws.String(d.Get("firehose_arn").(string)), IncludeLinkedAccountsMetrics: aws.Bool(d.Get("include_linked_accounts_metrics").(bool)), Name: aws.String(d.Id()), - OutputFormat: aws.String(d.Get("output_format").(string)), + OutputFormat: types.MetricStreamOutputFormat(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)) + input.ExcludeFilters = expandMetricStreamFilters(v.(*schema.Set).List()) } if v, ok := d.GetOk("include_filter"); ok && v.(*schema.Set).Len() > 0 { - input.IncludeFilters = expandMetricStreamFilters(v.(*schema.Set)) + input.IncludeFilters = expandMetricStreamFilters(v.(*schema.Set).List()) } if v, ok := d.GetOk("statistics_configuration"); ok && v.(*schema.Set).Len() > 0 { - input.StatisticsConfigurations = expandMetricStreamStatisticsConfigurations(v.(*schema.Set)) + input.StatisticsConfigurations = expandMetricStreamStatisticsConfigurations(v.(*schema.Set).List()) } - _, err := conn.PutMetricStreamWithContext(ctx, input) + _, err := conn.PutMetricStream(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "updating CloudWatch Metric Stream (%s): %s", d.Id(), err) @@ -353,11 +348,10 @@ func resourceMetricStreamUpdate(ctx context.Context, d *schema.ResourceData, met func resourceMetricStreamDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) log.Printf("[INFO] Deleting CloudWatch Metric Stream: %s", d.Id()) - _, err := conn.DeleteMetricStreamWithContext(ctx, &cloudwatch.DeleteMetricStreamInput{ + _, err := conn.DeleteMetricStream(ctx, &cloudwatch.DeleteMetricStreamInput{ Name: aws.String(d.Id()), }) @@ -372,14 +366,14 @@ func resourceMetricStreamDelete(ctx context.Context, d *schema.ResourceData, met return diags } -func FindMetricStreamByName(ctx context.Context, conn *cloudwatch.CloudWatch, name string) (*cloudwatch.GetMetricStreamOutput, error) { +func findMetricStreamByName(ctx context.Context, conn *cloudwatch.Client, name string) (*cloudwatch.GetMetricStreamOutput, error) { input := &cloudwatch.GetMetricStreamInput{ Name: aws.String(name), } - output, err := conn.GetMetricStreamWithContext(ctx, input) + output, err := conn.GetMetricStream(ctx, input) - if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFoundException) { + if errs.IsA[*types.ResourceNotFound](err) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, @@ -397,9 +391,9 @@ func FindMetricStreamByName(ctx context.Context, conn *cloudwatch.CloudWatch, na return output, nil } -func statusMetricStream(ctx context.Context, conn *cloudwatch.CloudWatch, name string) retry.StateRefreshFunc { +func statusMetricStream(ctx context.Context, conn *cloudwatch.Client, name string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := FindMetricStreamByName(ctx, conn, name) + output, err := findMetricStreamByName(ctx, conn, name) if tfresource.NotFound(err) { return nil, "", nil @@ -409,7 +403,7 @@ func statusMetricStream(ctx context.Context, conn *cloudwatch.CloudWatch, name s return nil, "", err } - return output, aws.StringValue(output.State), nil + return output, aws.ToString(output.State), nil } } @@ -418,7 +412,7 @@ const ( metricStreamStateStopped = "stopped" ) -func waitMetricStreamDeleted(ctx context.Context, conn *cloudwatch.CloudWatch, name string, timeout time.Duration) (*cloudwatch.GetMetricStreamOutput, error) { +func waitMetricStreamDeleted(ctx context.Context, conn *cloudwatch.Client, name string, timeout time.Duration) (*cloudwatch.GetMetricStreamOutput, error) { stateConf := &retry.StateChangeConf{ Pending: []string{metricStreamStateRunning, metricStreamStateStopped}, Target: []string{}, @@ -435,7 +429,7 @@ func waitMetricStreamDeleted(ctx context.Context, conn *cloudwatch.CloudWatch, n return nil, err } -func waitMetricStreamRunning(ctx context.Context, conn *cloudwatch.CloudWatch, name string, timeout time.Duration) (*cloudwatch.GetMetricStreamOutput, error) { //nolint:unparam +func waitMetricStreamRunning(ctx context.Context, conn *cloudwatch.Client, name string, timeout time.Duration) (*cloudwatch.GetMetricStreamOutput, error) { //nolint:unparam stateConf := &retry.StateChangeConf{ Pending: []string{metricStreamStateStopped}, Target: []string{metricStreamStateRunning}, @@ -459,120 +453,161 @@ func validateMetricStreamName(v interface{}, k string) (ws []string, errors []er )(v, k) } -func expandMetricStreamFilters(s *schema.Set) []*cloudwatch.MetricStreamFilter { - var filters []*cloudwatch.MetricStreamFilter +func expandMetricStreamFilters(tfList []interface{}) []types.MetricStreamFilter { + var apiObjects []types.MetricStreamFilter + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } - for _, filterRaw := range s.List() { - filter := &cloudwatch.MetricStreamFilter{} - mFilter := filterRaw.(map[string]interface{}) + apiObject := types.MetricStreamFilter{} - if v, ok := mFilter["metric_names"].(*schema.Set); ok && v.Len() > 0 { - filter.MetricNames = flex.ExpandStringSet(v) + if v, ok := tfMap["metric_names"].(*schema.Set); ok && v.Len() > 0 { + apiObject.MetricNames = flex.ExpandStringValueSet(v) } - if v, ok := mFilter["namespace"].(string); ok && v != "" { - filter.Namespace = aws.String(v) + + if v, ok := tfMap["namespace"].(string); ok && v != "" { + apiObject.Namespace = aws.String(v) } - filters = append(filters, filter) + + apiObjects = append(apiObjects, apiObject) } - return filters -} + if len(apiObjects) == 0 { + return nil + } -func flattenMetricStreamFilters(s []*cloudwatch.MetricStreamFilter) []map[string]interface{} { - filters := make([]map[string]interface{}, 0) + return apiObjects +} - for _, bd := range s { - if bd.Namespace != nil { - stage := make(map[string]interface{}) - stage["metric_names"] = aws.StringValueSlice(bd.MetricNames) - stage["namespace"] = aws.StringValue(bd.Namespace) - filters = append(filters, stage) - } +func flattenMetricStreamFilters(apiObjects []types.MetricStreamFilter) []interface{} { + if len(apiObjects) == 0 { + return nil } - if len(filters) > 0 { - return filters + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject.Namespace != nil { + tfMap := map[string]interface{}{ + "metric_names": apiObject.MetricNames, + } + + if v := apiObject.Namespace; v != nil { + tfMap["namespace"] = aws.ToString(v) + } + + tfList = append(tfList, tfMap) + } } - return nil + return tfList } -func expandMetricStreamStatisticsConfigurations(s *schema.Set) []*cloudwatch.MetricStreamStatisticsConfiguration { - var configurations []*cloudwatch.MetricStreamStatisticsConfiguration +func expandMetricStreamStatisticsConfigurations(tfList []interface{}) []types.MetricStreamStatisticsConfiguration { + var apiObjects []types.MetricStreamStatisticsConfiguration - for _, configurationRaw := range s.List() { - configuration := &cloudwatch.MetricStreamStatisticsConfiguration{} - mConfiguration := configurationRaw.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } - if v, ok := mConfiguration["additional_statistics"].(*schema.Set); ok && v.Len() > 0 { - configuration.AdditionalStatistics = flex.ExpandStringSet(v) + apiObject := types.MetricStreamStatisticsConfiguration{} + + if v, ok := tfMap["additional_statistics"].(*schema.Set); ok && v.Len() > 0 { + apiObject.AdditionalStatistics = flex.ExpandStringValueSet(v) } - if v, ok := mConfiguration["include_metric"].(*schema.Set); ok && v.Len() > 0 { - configuration.IncludeMetrics = expandMetricStreamStatisticsConfigurationsIncludeMetrics(v) + if v, ok := tfMap["include_metric"].(*schema.Set); ok && v.Len() > 0 { + apiObject.IncludeMetrics = expandMetricStreamStatisticsConfigurationsIncludeMetrics(v.List()) } - configurations = append(configurations, configuration) + apiObjects = append(apiObjects, apiObject) } - if len(configurations) > 0 { - return configurations + if len(apiObjects) == 0 { + return nil } - return nil + return apiObjects } -func expandMetricStreamStatisticsConfigurationsIncludeMetrics(metrics *schema.Set) []*cloudwatch.MetricStreamStatisticsMetric { - var includeMetrics []*cloudwatch.MetricStreamStatisticsMetric +func expandMetricStreamStatisticsConfigurationsIncludeMetrics(tfList []interface{}) []types.MetricStreamStatisticsMetric { + var apiObjects []types.MetricStreamStatisticsMetric + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } - for _, metricRaw := range metrics.List() { - metric := &cloudwatch.MetricStreamStatisticsMetric{} - mMetric := metricRaw.(map[string]interface{}) + apiObject := types.MetricStreamStatisticsMetric{} - if v, ok := mMetric["metric_name"].(string); ok && v != "" { - metric.MetricName = aws.String(v) + if v, ok := tfMap["metric_name"].(string); ok && v != "" { + apiObject.MetricName = aws.String(v) } - if v, ok := mMetric["namespace"].(string); ok && v != "" { - metric.Namespace = aws.String(v) + if v, ok := tfMap["namespace"].(string); ok && v != "" { + apiObject.Namespace = aws.String(v) } - includeMetrics = append(includeMetrics, metric) + apiObjects = append(apiObjects, apiObject) } - if len(includeMetrics) > 0 { - return includeMetrics + if len(apiObjects) == 0 { + return nil } - return nil + return apiObjects } -func flattenMetricStreamStatisticsConfigurations(configurations []*cloudwatch.MetricStreamStatisticsConfiguration) []map[string]interface{} { - flatConfigurations := make([]map[string]interface{}, len(configurations)) +func flattenMetricStreamStatisticsConfigurations(apiObjects []types.MetricStreamStatisticsConfiguration) []interface{} { + if len(apiObjects) == 0 { + return nil + } - for i, configuration := range configurations { - flatConfiguration := map[string]interface{}{ - "additional_statistics": flex.FlattenStringSet(configuration.AdditionalStatistics), - "include_metric": flattenMetricStreamStatisticsConfigurationsIncludeMetrics(configuration.IncludeMetrics), + var tfList []interface{} + + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{} + + if v := apiObject.AdditionalStatistics; v != nil { + tfMap["additional_statistics"] = flex.FlattenStringValueSet(v) } - flatConfigurations[i] = flatConfiguration + if v := apiObject.IncludeMetrics; v != nil { + tfMap["include_metric"] = flattenMetricStreamStatisticsConfigurationsIncludeMetrics(v) + } + + tfList = append(tfList, tfMap) } - return flatConfigurations + return tfList } -func flattenMetricStreamStatisticsConfigurationsIncludeMetrics(metrics []*cloudwatch.MetricStreamStatisticsMetric) []map[string]interface{} { - flatMetrics := make([]map[string]interface{}, len(metrics)) +func flattenMetricStreamStatisticsConfigurationsIncludeMetrics(apiObjects []types.MetricStreamStatisticsMetric) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{} + + if v := apiObject.MetricName; v != nil { + tfMap["metric_name"] = aws.ToString(v) + } - for i, metric := range metrics { - flatMetric := map[string]interface{}{ - "metric_name": aws.StringValue(metric.MetricName), - "namespace": aws.StringValue(metric.Namespace), + if v := apiObject.Namespace; v != nil { + tfMap["namespace"] = aws.ToString(v) } - flatMetrics[i] = flatMetric + tfList = append(tfList, tfMap) } - return flatMetrics + return tfList } diff --git a/internal/service/cloudwatch/service_package_gen.go b/internal/service/cloudwatch/service_package_gen.go index 1d2e0b4754f..dfce9ca867a 100644 --- a/internal/service/cloudwatch/service_package_gen.go +++ b/internal/service/cloudwatch/service_package_gen.go @@ -50,9 +50,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceMetricStream, + Factory: resourceMetricStream, TypeName: "aws_cloudwatch_metric_stream", - Name: "Metric Alarm", + Name: "Metric Stream", Tags: &types.ServicePackageResourceTags{ IdentifierAttribute: "arn", }, From f40a484a205640375274d08744d1475d9698b5de Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 30 Jan 2024 13:13:59 -0500 Subject: [PATCH 06/12] r/aws_cloudwatch_metric_alarm: Migrate to AWS SDK for Go v2. --- internal/service/cloudwatch/exports_test.go | 2 + internal/service/cloudwatch/metric_alarm.go | 391 ++++++++++-------- .../service/cloudwatch/service_package_gen.go | 2 +- 3 files changed, 215 insertions(+), 180 deletions(-) diff --git a/internal/service/cloudwatch/exports_test.go b/internal/service/cloudwatch/exports_test.go index 4e499214fe6..3283c282f92 100644 --- a/internal/service/cloudwatch/exports_test.go +++ b/internal/service/cloudwatch/exports_test.go @@ -6,8 +6,10 @@ package cloudwatch // Exports for use in tests only. var ( ResourceDashboard = resourceDashboard + ResourceMetricAlarm = resourceMetricAlarm ResourceMetricStream = resourceMetricStream FindDashboardByName = findDashboardByName + FindMetricAlarmByName = findMetricAlarmByName FindMetricStreamByName = findMetricStreamByName ) diff --git a/internal/service/cloudwatch/metric_alarm.go b/internal/service/cloudwatch/metric_alarm.go index a5af1ad9ca2..7a2dd9e429f 100644 --- a/internal/service/cloudwatch/metric_alarm.go +++ b/internal/service/cloudwatch/metric_alarm.go @@ -5,21 +5,23 @@ package cloudwatch import ( "context" - "fmt" + "errors" "log" "github.com/YakDriver/regexache" - "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/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "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/enum" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" 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" @@ -28,7 +30,7 @@ import ( // @SDKResource("aws_cloudwatch_metric_alarm", name="Metric Alarm") // @Tags(identifierAttribute="arn") -func ResourceMetricAlarm() *schema.Resource { +func resourceMetricAlarm() *schema.Resource { //lintignore:R011 return &schema.Resource{ CreateWithoutTimeout: resourceMetricAlarmCreate, @@ -76,9 +78,9 @@ func ResourceMetricAlarm() *schema.Resource { Computed: true, }, "comparison_operator": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(cloudwatch.ComparisonOperator_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.ComparisonOperator](), }, "datapoints_to_alarm": { Type: schema.TypeInt, @@ -136,11 +138,6 @@ func ResourceMetricAlarm() *schema.Resource { ConflictsWith: []string{"metric_name"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringLenBetween(1, 255), - }, "account_id": { Type: schema.TypeString, Optional: true, @@ -151,6 +148,11 @@ func ResourceMetricAlarm() *schema.Resource { Optional: true, ValidateFunc: validation.StringLenBetween(1, 1024), }, + "id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, "metric": { Type: schema.TypeList, MaxItems: 1, @@ -186,19 +188,21 @@ func ResourceMetricAlarm() *schema.Resource { "stat": { Type: schema.TypeString, Required: true, - ValidateFunc: validation.Any( - validation.StringInSlice(cloudwatch.Statistic_Values(), false), - validation.StringMatch( - // doesn't catch: PR with %-values provided, TM/WM/PR/TC/TS with no values provided - regexache.MustCompile(`^((p|(tm)|(wm)|(tc)|(ts))((\d{1,2}(\.\d{1,2})?)|(100))|(IQM)|(((TM)|(WM)|(PR)|(TC)|(TS)))\((\d+(\.\d+)?%?)?:(\d+(\.\d+)?%?)?\))$`), - "invalid statistic, see: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Statistics-definitions.html", + ValidateDiagFunc: validation.AnyDiag( + enum.Validate[types.Statistic](), + validation.ToDiagFunc( + validation.StringMatch( + // doesn't catch: PR with %-values provided, TM/WM/PR/TC/TS with no values provided + regexache.MustCompile(`^((p|(tm)|(wm)|(tc)|(ts))((\d{1,2}(\.\d{1,2})?)|(100))|(IQM)|(((TM)|(WM)|(PR)|(TC)|(TS)))\((\d+(\.\d+)?%?)?:(\d+(\.\d+)?%?)?\))$`), + "invalid statistic, see: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Statistics-definitions.html", + ), ), ), }, "unit": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(cloudwatch.StandardUnit_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.StandardUnit](), }, }, }, @@ -254,10 +258,10 @@ func ResourceMetricAlarm() *schema.Resource { ), }, "statistic": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"extended_statistic", "metric_query"}, - ValidateFunc: validation.StringInSlice(cloudwatch.Statistic_Values(), false), + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"extended_statistic", "metric_query"}, + ValidateDiagFunc: enum.Validate[types.Statistic](), }, names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), @@ -279,60 +283,56 @@ func ResourceMetricAlarm() *schema.Resource { ValidateFunc: validation.StringInSlice(missingData_Values(), true), }, "unit": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(cloudwatch.StandardUnit_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.StandardUnit](), }, }, - CustomizeDiff: verify.SetTagsDiff, - } -} - -func validMetricAlarm(d *schema.ResourceData) error { - _, metricNameOk := d.GetOk("metric_name") - _, statisticOk := d.GetOk("statistic") - _, extendedStatisticOk := d.GetOk("extended_statistic") + CustomizeDiff: customdiff.All( + verify.SetTagsDiff, + func(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { + _, metricNameOk := diff.GetOk("metric_name") + _, statisticOk := diff.GetOk("statistic") + _, extendedStatisticOk := diff.GetOk("extended_statistic") - if metricNameOk && ((!statisticOk && !extendedStatisticOk) || (statisticOk && extendedStatisticOk)) { - return fmt.Errorf("One of `statistic` or `extended_statistic` must be set for a cloudwatch metric alarm") - } + if metricNameOk && ((!statisticOk && !extendedStatisticOk) || (statisticOk && extendedStatisticOk)) { + return errors.New("One of `statistic` or `extended_statistic` must be set for a cloudwatch metric alarm") + } - if v := d.Get("metric_query"); v != nil { - for _, v := range v.(*schema.Set).List() { - metricQueryResource := v.(map[string]interface{}) - if v, ok := metricQueryResource["expression"]; ok && v.(string) != "" { - if v := metricQueryResource["metric"]; v != nil { - if len(v.([]interface{})) > 0 { - return fmt.Errorf("No metric_query may have both `expression` and a `metric` specified") + if v := diff.Get("metric_query"); v != nil { + for _, v := range v.(*schema.Set).List() { + tfMap := v.(map[string]interface{}) + if v, ok := tfMap["expression"]; ok && v.(string) != "" { + if v := tfMap["metric"]; v != nil { + if len(v.([]interface{})) > 0 { + return errors.New("No metric_query may have both `expression` and a `metric` specified") + } + } + } } } - } - } - } - return nil + return nil + }, + ), + } } func resourceMetricAlarmCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) - - err := validMetricAlarm(d) - if err != nil { - return sdkdiag.AppendErrorf(diags, "creating CloudWatch Metric Alarm (%s): %s", d.Get("alarm_name").(string), err) - } + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) name := d.Get("alarm_name").(string) input := expandPutMetricAlarmInput(ctx, d) - _, err = conn.PutMetricAlarmWithContext(ctx, input) + _, err := conn.PutMetricAlarm(ctx, input) // Some partitions (e.g. ISO) may not support tag-on-create. - if input.Tags != nil && errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { + if input.Tags != nil && errs.IsUnsupportedOperationInPartitionError(meta.(*conns.AWSClient).Partition, err) { input.Tags = nil - _, err = conn.PutMetricAlarmWithContext(ctx, input) + _, err = conn.PutMetricAlarm(ctx, input) } if err != nil { @@ -343,16 +343,16 @@ func resourceMetricAlarmCreate(ctx context.Context, d *schema.ResourceData, meta // For partitions not supporting tag-on-create, attempt tag after create. if tags := getTagsIn(ctx); input.Tags == nil && len(tags) > 0 { - alarm, err := FindMetricAlarmByName(ctx, conn, d.Id()) + alarm, err := findMetricAlarmByName(ctx, conn, d.Id()) if err != nil { return sdkdiag.AppendErrorf(diags, "reading CloudWatch Metric Alarm (%s): %s", d.Id(), err) } - err = createTags(ctx, conn, aws.StringValue(alarm.AlarmArn), tags) + err = createTags(ctx, conn, aws.ToString(alarm.AlarmArn), tags) // If default tags only, continue. Otherwise, error. - if v, ok := d.GetOk(names.AttrTags); (!ok || len(v.(map[string]interface{})) == 0) && errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { + if v, ok := d.GetOk(names.AttrTags); (!ok || len(v.(map[string]interface{})) == 0) && errs.IsUnsupportedOperationInPartitionError(meta.(*conns.AWSClient).Partition, err) { return append(diags, resourceMetricAlarmRead(ctx, d, meta)...) } @@ -366,9 +366,9 @@ func resourceMetricAlarmCreate(ctx context.Context, d *schema.ResourceData, meta func resourceMetricAlarmRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) - alarm, err := FindMetricAlarmByName(ctx, conn, d.Id()) + alarm, err := findMetricAlarmByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] CloudWatch Metric Alarm %s not found, removing from state", d.Id()) @@ -381,7 +381,7 @@ func resourceMetricAlarmRead(ctx context.Context, d *schema.ResourceData, meta i } d.Set("actions_enabled", alarm.ActionsEnabled) - d.Set("alarm_actions", aws.StringValueSlice(alarm.AlarmActions)) + d.Set("alarm_actions", alarm.AlarmActions) d.Set("alarm_description", alarm.AlarmDescription) d.Set("alarm_name", alarm.AlarmName) d.Set("arn", alarm.AlarmArn) @@ -393,7 +393,7 @@ func resourceMetricAlarmRead(ctx context.Context, d *schema.ResourceData, meta i d.Set("evaluate_low_sample_count_percentiles", alarm.EvaluateLowSampleCountPercentile) d.Set("evaluation_periods", alarm.EvaluationPeriods) d.Set("extended_statistic", alarm.ExtendedStatistic) - d.Set("insufficient_data_actions", aws.StringValueSlice(alarm.InsufficientDataActions)) + d.Set("insufficient_data_actions", alarm.InsufficientDataActions) d.Set("metric_name", alarm.MetricName) if len(alarm.Metrics) > 0 { if err := d.Set("metric_query", flattenMetricAlarmMetrics(alarm.Metrics)); err != nil { @@ -401,7 +401,7 @@ func resourceMetricAlarmRead(ctx context.Context, d *schema.ResourceData, meta i } } d.Set("namespace", alarm.Namespace) - d.Set("ok_actions", aws.StringValueSlice(alarm.OKActions)) + d.Set("ok_actions", alarm.OKActions) d.Set("period", alarm.Period) d.Set("statistic", alarm.Statistic) d.Set("threshold", alarm.Threshold) @@ -418,12 +418,12 @@ func resourceMetricAlarmRead(ctx context.Context, d *schema.ResourceData, meta i func resourceMetricAlarmUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) if d.HasChangesExcept("tags", "tags_all") { input := expandPutMetricAlarmInput(ctx, d) - _, err := conn.PutMetricAlarmWithContext(ctx, input) + _, err := conn.PutMetricAlarm(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "updating CloudWatch Metric Alarm (%s): %s", d.Id(), err) @@ -435,14 +435,14 @@ func resourceMetricAlarmUpdate(ctx context.Context, d *schema.ResourceData, meta func resourceMetricAlarmDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) log.Printf("[INFO] Deleting CloudWatch Metric Alarm: %s", d.Id()) - _, err := conn.DeleteAlarmsWithContext(ctx, &cloudwatch.DeleteAlarmsInput{ - AlarmNames: aws.StringSlice([]string{d.Id()}), + _, err := conn.DeleteAlarms(ctx, &cloudwatch.DeleteAlarmsInput{ + AlarmNames: tfslices.Of(d.Id()), }) - if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFoundException) { + if errs.IsA[*types.ResourceNotFoundException](err) { return diags } @@ -453,41 +453,30 @@ func resourceMetricAlarmDelete(ctx context.Context, d *schema.ResourceData, meta return diags } -func FindMetricAlarmByName(ctx context.Context, conn *cloudwatch.CloudWatch, name string) (*cloudwatch.MetricAlarm, error) { +func findMetricAlarmByName(ctx context.Context, conn *cloudwatch.Client, name string) (*types.MetricAlarm, error) { input := &cloudwatch.DescribeAlarmsInput{ - AlarmNames: aws.StringSlice([]string{name}), - AlarmTypes: aws.StringSlice([]string{cloudwatch.AlarmTypeMetricAlarm}), + AlarmNames: tfslices.Of(name), + AlarmTypes: tfslices.Of(types.AlarmTypeMetricAlarm), } - output, err := conn.DescribeAlarmsWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFound) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } + output, err := conn.DescribeAlarms(ctx, input) if err != nil { return nil, err } - if output == nil || len(output.MetricAlarms) == 0 || output.MetricAlarms[0] == nil { + if output == nil { return nil, tfresource.NewEmptyResultError(input) } - if count := len(output.MetricAlarms); count > 1 { - return nil, tfresource.NewTooManyResultsError(count, input) - } - - return output.MetricAlarms[0], nil + return tfresource.AssertSingleValueResult(output.MetricAlarms) } func expandPutMetricAlarmInput(ctx context.Context, d *schema.ResourceData) *cloudwatch.PutMetricAlarmInput { apiObject := &cloudwatch.PutMetricAlarmInput{ AlarmName: aws.String(d.Get("alarm_name").(string)), - ComparisonOperator: aws.String(d.Get("comparison_operator").(string)), - EvaluationPeriods: aws.Int64(int64(d.Get("evaluation_periods").(int))), + ComparisonOperator: types.ComparisonOperator(d.Get("comparison_operator").(string)), + EvaluationPeriods: aws.Int32(int32(d.Get("evaluation_periods").(int))), Tags: getTagsIn(ctx), TreatMissingData: aws.String(d.Get("treat_missing_data").(string)), } @@ -496,8 +485,8 @@ func expandPutMetricAlarmInput(ctx context.Context, d *schema.ResourceData) *clo apiObject.ActionsEnabled = aws.Bool(v.(bool)) } - if v, ok := d.GetOk("alarm_actions"); ok { - apiObject.AlarmActions = flex.ExpandStringSet(v.(*schema.Set)) + if v, ok := d.GetOk("alarm_actions"); ok && v.(*schema.Set).Len() > 0 { + apiObject.AlarmActions = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("alarm_description"); ok { @@ -505,10 +494,10 @@ func expandPutMetricAlarmInput(ctx context.Context, d *schema.ResourceData) *clo } if v, ok := d.GetOk("datapoints_to_alarm"); ok { - apiObject.DatapointsToAlarm = aws.Int64(int64(v.(int))) + apiObject.DatapointsToAlarm = aws.Int32(int32(v.(int))) } - if v, ok := d.GetOk("dimensions"); ok { + if v, ok := d.GetOk("dimensions"); ok && len(v.(map[string]interface{})) > 0 { apiObject.Dimensions = expandMetricAlarmDimensions(v.(map[string]interface{})) } @@ -520,32 +509,32 @@ func expandPutMetricAlarmInput(ctx context.Context, d *schema.ResourceData) *clo apiObject.ExtendedStatistic = aws.String(v.(string)) } - if v, ok := d.GetOk("insufficient_data_actions"); ok { - apiObject.InsufficientDataActions = flex.ExpandStringSet(v.(*schema.Set)) + if v, ok := d.GetOk("insufficient_data_actions"); ok && v.(*schema.Set).Len() > 0 { + apiObject.InsufficientDataActions = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("metric_name"); ok { apiObject.MetricName = aws.String(v.(string)) } - if v := d.Get("metric_query"); v != nil { - apiObject.Metrics = expandMetricAlarmMetrics(v.(*schema.Set)) + if v, ok := d.GetOk("metric_query"); ok && v.(*schema.Set).Len() > 0 { + apiObject.Metrics = expandMetricAlarmMetrics(v.(*schema.Set).List()) } if v, ok := d.GetOk("namespace"); ok { apiObject.Namespace = aws.String(v.(string)) } - if v, ok := d.GetOk("ok_actions"); ok { - apiObject.OKActions = flex.ExpandStringSet(v.(*schema.Set)) + if v, ok := d.GetOk("ok_actions"); ok && v.(*schema.Set).Len() > 0 { + apiObject.OKActions = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("period"); ok { - apiObject.Period = aws.Int64(int64(v.(int))) + apiObject.Period = aws.Int32(int32(v.(int))) } if v, ok := d.GetOk("statistic"); ok { - apiObject.Statistic = aws.String(v.(string)) + apiObject.Statistic = types.Statistic(v.(string)) } if v, ok := d.GetOk("threshold_metric_id"); ok { @@ -555,124 +544,168 @@ func expandPutMetricAlarmInput(ctx context.Context, d *schema.ResourceData) *clo } if v, ok := d.GetOk("unit"); ok { - apiObject.Unit = aws.String(v.(string)) + apiObject.Unit = types.StandardUnit(v.(string)) } return apiObject } -func flattenMetricAlarmDimensions(dims []*cloudwatch.Dimension) map[string]interface{} { - flatDims := make(map[string]interface{}) - for _, d := range dims { - flatDims[aws.StringValue(d.Name)] = aws.StringValue(d.Value) +func flattenMetricAlarmDimensions(apiObjects []types.Dimension) map[string]interface{} { + tfMap := map[string]interface{}{} + + for _, apiObject := range apiObjects { + tfMap[aws.ToString(apiObject.Name)] = aws.ToString(apiObject.Value) } - return flatDims + + return tfMap } -func flattenMetricAlarmMetrics(metrics []*cloudwatch.MetricDataQuery) []map[string]interface{} { - metricQueries := make([]map[string]interface{}, 0) - for _, mq := range metrics { - metricQuery := map[string]interface{}{ - "account_id": aws.StringValue(mq.AccountId), - "expression": aws.StringValue(mq.Expression), - "id": aws.StringValue(mq.Id), - "label": aws.StringValue(mq.Label), - "return_data": aws.BoolValue(mq.ReturnData), +func flattenMetricAlarmMetrics(apiObjects []types.MetricDataQuery) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{ + "account_id": aws.ToString(apiObject.AccountId), + "expression": aws.ToString(apiObject.Expression), + "id": aws.ToString(apiObject.Id), + "label": aws.ToString(apiObject.Label), + "return_data": aws.ToBool(apiObject.ReturnData), } - if mq.MetricStat != nil { - metric := flattenMetricAlarmMetricsMetricStat(mq.MetricStat) - metricQuery["metric"] = []interface{}{metric} + + if v := apiObject.MetricStat; v != nil { + tfMap["metric"] = []interface{}{flattenMetricAlarmMetricsMetricStat(v)} } - if mq.Period != nil { - metricQuery["period"] = aws.Int64Value(mq.Period) + + if apiObject.Period != nil { + tfMap["period"] = aws.ToInt32(apiObject.Period) } - metricQueries = append(metricQueries, metricQuery) + + tfList = append(tfList, tfMap) } - return metricQueries + return tfList } -func flattenMetricAlarmMetricsMetricStat(ms *cloudwatch.MetricStat) map[string]interface{} { - msm := ms.Metric - metric := map[string]interface{}{ - "metric_name": aws.StringValue(msm.MetricName), - "namespace": aws.StringValue(msm.Namespace), - "period": int(aws.Int64Value(ms.Period)), - "stat": aws.StringValue(ms.Stat), - "unit": aws.StringValue(ms.Unit), - "dimensions": flattenMetricAlarmDimensions(msm.Dimensions), +func flattenMetricAlarmMetricsMetricStat(apiObject *types.MetricStat) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "period": aws.ToInt32(apiObject.Period), + "stat": aws.ToString(apiObject.Stat), + "unit": apiObject.Unit, } - return metric + if v := apiObject.Metric; v != nil { + tfMap["dimensions"] = flattenMetricAlarmDimensions(v.Dimensions) + tfMap["metric_name"] = aws.ToString(v.MetricName) + tfMap["namespace"] = aws.ToString(v.Namespace) + } + + return tfMap } -func expandMetricAlarmMetrics(v *schema.Set) []*cloudwatch.MetricDataQuery { - var metrics []*cloudwatch.MetricDataQuery +func expandMetricAlarmMetrics(tfList []interface{}) []types.MetricDataQuery { + var apiObjects []types.MetricDataQuery + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } - for _, v := range v.List() { - metricQueryResource := v.(map[string]interface{}) - id := metricQueryResource["id"].(string) + id := tfMap["id"].(string) if id == "" { continue } - metricQuery := cloudwatch.MetricDataQuery{ + + apiObject := types.MetricDataQuery{ Id: aws.String(id), } - if v, ok := metricQueryResource["expression"]; ok && v.(string) != "" { - metricQuery.Expression = aws.String(v.(string)) + + if v, ok := tfMap["account_id"]; ok && v.(string) != "" { + apiObject.AccountId = aws.String(v.(string)) } - if v, ok := metricQueryResource["label"]; ok && v.(string) != "" { - metricQuery.Label = aws.String(v.(string)) + + if v, ok := tfMap["expression"]; ok && v.(string) != "" { + apiObject.Expression = aws.String(v.(string)) } - if v, ok := metricQueryResource["return_data"]; ok { - metricQuery.ReturnData = aws.Bool(v.(bool)) + + if v, ok := tfMap["label"]; ok && v.(string) != "" { + apiObject.Label = aws.String(v.(string)) } - if v := metricQueryResource["metric"]; v != nil && len(v.([]interface{})) > 0 { - metricQuery.MetricStat = expandMetricAlarmMetricsMetric(v.([]interface{})) + + if v, ok := tfMap["return_data"]; ok { + apiObject.ReturnData = aws.Bool(v.(bool)) } - if v, ok := metricQueryResource["period"]; ok && v.(int) != 0 { - metricQuery.Period = aws.Int64(int64(v.(int))) + + if v, ok := tfMap["metric"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.MetricStat = expandMetricAlarmMetricsMetric(v[0].(map[string]interface{})) } - if v, ok := metricQueryResource["account_id"]; ok && v.(string) != "" { - metricQuery.AccountId = aws.String(v.(string)) + + if v, ok := tfMap["period"]; ok && v.(int) != 0 { + apiObject.Period = aws.Int32(int32(v.(int))) } - metrics = append(metrics, &metricQuery) + + apiObjects = append(apiObjects, apiObject) } - return metrics + + if len(apiObjects) == 0 { + return nil + } + + return apiObjects } -func expandMetricAlarmMetricsMetric(v []interface{}) *cloudwatch.MetricStat { - metricResource := v[0].(map[string]interface{}) - metric := cloudwatch.Metric{ - MetricName: aws.String(metricResource["metric_name"].(string)), +func expandMetricAlarmMetricsMetric(tfMap map[string]interface{}) *types.MetricStat { + if tfMap == nil { + return nil } - metricStat := cloudwatch.MetricStat{ - Metric: &metric, - Stat: aws.String(metricResource["stat"].(string)), + + apiObject := &types.MetricStat{ + Metric: &types.Metric{ + MetricName: aws.String(tfMap["metric_name"].(string)), + }, + Stat: aws.String(tfMap["stat"].(string)), } - if v, ok := metricResource["namespace"]; ok && v.(string) != "" { - metric.Namespace = aws.String(v.(string)) + + if v, ok := tfMap["dimensions"].(map[string]interface{}); ok && len(v) > 0 { + apiObject.Metric.Dimensions = expandMetricAlarmDimensions(v) } - if v, ok := metricResource["period"]; ok { - metricStat.Period = aws.Int64(int64(v.(int))) + + if v, ok := tfMap["namespace"]; ok && v.(string) != "" { + apiObject.Metric.Namespace = aws.String(v.(string)) } - if v, ok := metricResource["unit"]; ok && v.(string) != "" { - metricStat.Unit = aws.String(v.(string)) + + if v, ok := tfMap["period"]; ok { + apiObject.Period = aws.Int32(int32(v.(int))) } - if v, ok := metricResource["dimensions"]; ok { - metric.Dimensions = expandMetricAlarmDimensions(v.(map[string]interface{})) + + if v, ok := tfMap["unit"]; ok && v.(string) != "" { + apiObject.Unit = types.StandardUnit(v.(string)) } - return &metricStat + return apiObject } -func expandMetricAlarmDimensions(dims map[string]interface{}) []*cloudwatch.Dimension { - var dimensions []*cloudwatch.Dimension - for k, v := range dims { - dimensions = append(dimensions, &cloudwatch.Dimension{ +func expandMetricAlarmDimensions(tfMap map[string]interface{}) []types.Dimension { + if len(tfMap) == 0 { + return nil + } + + var apiObjects []types.Dimension + + for k, v := range tfMap { + apiObjects = append(apiObjects, types.Dimension{ Name: aws.String(k), Value: aws.String(v.(string)), }) } - return dimensions + + return apiObjects } diff --git a/internal/service/cloudwatch/service_package_gen.go b/internal/service/cloudwatch/service_package_gen.go index dfce9ca867a..f0db2748b5e 100644 --- a/internal/service/cloudwatch/service_package_gen.go +++ b/internal/service/cloudwatch/service_package_gen.go @@ -42,7 +42,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Dashboard", }, { - Factory: ResourceMetricAlarm, + Factory: resourceMetricAlarm, TypeName: "aws_cloudwatch_metric_alarm", Name: "Metric Alarm", Tags: &types.ServicePackageResourceTags{ From 079fd47e1c72a133eeea3f6453aee0ee6f0164ac Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 30 Jan 2024 13:26:43 -0500 Subject: [PATCH 07/12] r/aws_cloudwatch_composite_alarm: Migrate to AWS SDK for Go v2. --- .../service/cloudwatch/composite_alarm.go | 126 ++++++++---------- internal/service/cloudwatch/exports_test.go | 14 +- .../service/cloudwatch/service_package_gen.go | 2 +- 3 files changed, 68 insertions(+), 74 deletions(-) diff --git a/internal/service/cloudwatch/composite_alarm.go b/internal/service/cloudwatch/composite_alarm.go index 7c7059d7571..4c7096388bd 100644 --- a/internal/service/cloudwatch/composite_alarm.go +++ b/internal/service/cloudwatch/composite_alarm.go @@ -7,17 +7,17 @@ import ( "context" "log" - "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/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "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/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" 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" @@ -26,7 +26,7 @@ import ( // @SDKResource("aws_cloudwatch_composite_alarm", name="Composite Alarm") // @Tags(identifierAttribute="arn") -func ResourceCompositeAlarm() *schema.Resource { +func resourceCompositeAlarm() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceCompositeAlarmCreate, ReadWithoutTimeout: resourceCompositeAlarmRead, @@ -68,7 +68,6 @@ func ResourceCompositeAlarm() *schema.Resource { "alarm_actions": { Type: schema.TypeSet, Optional: true, - Set: schema.HashString, MaxItems: 5, Elem: &schema.Schema{ Type: schema.TypeString, @@ -98,7 +97,6 @@ func ResourceCompositeAlarm() *schema.Resource { "insufficient_data_actions": { Type: schema.TypeSet, Optional: true, - Set: schema.HashString, MaxItems: 5, Elem: &schema.Schema{ Type: schema.TypeString, @@ -108,7 +106,6 @@ func ResourceCompositeAlarm() *schema.Resource { "ok_actions": { Type: schema.TypeSet, Optional: true, - Set: schema.HashString, MaxItems: 5, Elem: &schema.Schema{ Type: schema.TypeString, @@ -125,19 +122,18 @@ func ResourceCompositeAlarm() *schema.Resource { func resourceCompositeAlarmCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) name := d.Get("alarm_name").(string) input := expandPutCompositeAlarmInput(ctx, d) - _, err := conn.PutCompositeAlarmWithContext(ctx, input) + _, err := conn.PutCompositeAlarm(ctx, input) // Some partitions (e.g. ISO) may not support tag-on-create. - if input.Tags != nil && errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { + if input.Tags != nil && errs.IsUnsupportedOperationInPartitionError(meta.(*conns.AWSClient).Partition, err) { input.Tags = nil - _, err = conn.PutCompositeAlarmWithContext(ctx, input) + _, err = conn.PutCompositeAlarm(ctx, input) } if err != nil { @@ -148,16 +144,16 @@ func resourceCompositeAlarmCreate(ctx context.Context, d *schema.ResourceData, m // For partitions not supporting tag-on-create, attempt tag after create. if tags := getTagsIn(ctx); input.Tags == nil && len(tags) > 0 { - alarm, err := FindCompositeAlarmByName(ctx, conn, d.Id()) + alarm, err := findCompositeAlarmByName(ctx, conn, d.Id()) if err != nil { return sdkdiag.AppendErrorf(diags, "reading CloudWatch Composite Alarm (%s): %s", d.Id(), err) } - err = createTags(ctx, conn, aws.StringValue(alarm.AlarmArn), tags) + err = createTags(ctx, conn, aws.ToString(alarm.AlarmArn), tags) // If default tags only, continue. Otherwise, error. - if v, ok := d.GetOk(names.AttrTags); (!ok || len(v.(map[string]interface{})) == 0) && errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { + if v, ok := d.GetOk(names.AttrTags); (!ok || len(v.(map[string]interface{})) == 0) && errs.IsUnsupportedOperationInPartitionError(meta.(*conns.AWSClient).Partition, err) { return append(diags, resourceCompositeAlarmRead(ctx, d, meta)...) } @@ -171,10 +167,9 @@ func resourceCompositeAlarmCreate(ctx context.Context, d *schema.ResourceData, m func resourceCompositeAlarmRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) - - alarm, err := FindCompositeAlarmByName(ctx, conn, d.Id()) + alarm, err := findCompositeAlarmByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] CloudWatch Composite Alarm %s not found, removing from state", d.Id()) @@ -194,26 +189,25 @@ func resourceCompositeAlarmRead(ctx context.Context, d *schema.ResourceData, met } else { d.Set("actions_suppressor", nil) } - d.Set("alarm_actions", aws.StringValueSlice(alarm.AlarmActions)) + d.Set("alarm_actions", alarm.AlarmActions) d.Set("alarm_description", alarm.AlarmDescription) d.Set("alarm_name", alarm.AlarmName) d.Set("alarm_rule", alarm.AlarmRule) d.Set("arn", alarm.AlarmArn) - d.Set("insufficient_data_actions", aws.StringValueSlice(alarm.InsufficientDataActions)) - d.Set("ok_actions", aws.StringValueSlice(alarm.OKActions)) + d.Set("insufficient_data_actions", alarm.InsufficientDataActions) + d.Set("ok_actions", alarm.OKActions) return diags } func resourceCompositeAlarmUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) if d.HasChangesExcept("tags", "tags_all") { input := expandPutCompositeAlarmInput(ctx, d) - _, err := conn.PutCompositeAlarmWithContext(ctx, input) + _, err := conn.PutCompositeAlarm(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "updating CloudWatch Composite Alarm (%s): %s", d.Id(), err) @@ -225,15 +219,14 @@ func resourceCompositeAlarmUpdate(ctx context.Context, d *schema.ResourceData, m func resourceCompositeAlarmDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - - conn := meta.(*conns.AWSClient).CloudWatchConn(ctx) + conn := meta.(*conns.AWSClient).CloudWatchClient(ctx) log.Printf("[INFO] Deleting CloudWatch Composite Alarm: %s", d.Id()) - _, err := conn.DeleteAlarmsWithContext(ctx, &cloudwatch.DeleteAlarmsInput{ - AlarmNames: aws.StringSlice([]string{d.Id()}), + _, err := conn.DeleteAlarms(ctx, &cloudwatch.DeleteAlarmsInput{ + AlarmNames: tfslices.Of(d.Id()), }) - if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFound) { + if errs.IsA[*types.ResourceNotFoundException](err) { return diags } @@ -244,20 +237,13 @@ func resourceCompositeAlarmDelete(ctx context.Context, d *schema.ResourceData, m return diags } -func FindCompositeAlarmByName(ctx context.Context, conn *cloudwatch.CloudWatch, name string) (*cloudwatch.CompositeAlarm, error) { +func findCompositeAlarmByName(ctx context.Context, conn *cloudwatch.Client, name string) (*types.CompositeAlarm, error) { input := &cloudwatch.DescribeAlarmsInput{ - AlarmNames: aws.StringSlice([]string{name}), - AlarmTypes: aws.StringSlice([]string{cloudwatch.AlarmTypeCompositeAlarm}), + AlarmNames: tfslices.Of(name), + AlarmTypes: tfslices.Of(types.AlarmTypeCompositeAlarm), } - output, err := conn.DescribeAlarmsWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, cloudwatch.ErrCodeResourceNotFound) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } + output, err := conn.DescribeAlarms(ctx, input) if err != nil { return nil, err @@ -267,7 +253,7 @@ func FindCompositeAlarmByName(ctx context.Context, conn *cloudwatch.CloudWatch, return nil, tfresource.NewEmptyResultError(input) } - return tfresource.AssertSinglePtrResult(output.CompositeAlarms) + return tfresource.AssertSingleValueResult(output.CompositeAlarms) } func expandPutCompositeAlarmInput(ctx context.Context, d *schema.ResourceData) *cloudwatch.PutCompositeAlarmInput { @@ -276,12 +262,12 @@ func expandPutCompositeAlarmInput(ctx context.Context, d *schema.ResourceData) * Tags: getTagsIn(ctx), } - if v, ok := d.GetOk("alarm_actions"); ok { - apiObject.AlarmActions = flex.ExpandStringSet(v.(*schema.Set)) + if v, ok := d.GetOk("alarm_actions"); ok && v.(*schema.Set).Len() > 0 { + apiObject.AlarmActions = flex.ExpandStringValueSet(v.(*schema.Set)) } - if v, ok := d.GetOk("actions_suppressor"); ok && v != nil && len(v.([]interface{})) > 0 { - alarm := expandActionsSuppressor(v.([]interface{})) + if v, ok := d.GetOk("actions_suppressor"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + alarm := expandActionsSuppressor(v.([]interface{})[0].(map[string]interface{})) apiObject.ActionsSuppressor = alarm.ActionsSuppressor apiObject.ActionsSuppressorExtensionPeriod = alarm.ActionsSuppressorExtensionPeriod apiObject.ActionsSuppressorWaitPeriod = alarm.ActionsSuppressorWaitPeriod @@ -299,43 +285,49 @@ func expandPutCompositeAlarmInput(ctx context.Context, d *schema.ResourceData) * apiObject.AlarmRule = aws.String(v.(string)) } - if v, ok := d.GetOk("insufficient_data_actions"); ok { - apiObject.InsufficientDataActions = flex.ExpandStringSet(v.(*schema.Set)) + if v, ok := d.GetOk("insufficient_data_actions"); ok && v.(*schema.Set).Len() > 0 { + apiObject.InsufficientDataActions = flex.ExpandStringValueSet(v.(*schema.Set)) } - if v, ok := d.GetOk("ok_actions"); ok { - apiObject.OKActions = flex.ExpandStringSet(v.(*schema.Set)) + if v, ok := d.GetOk("ok_actions"); ok && v.(*schema.Set).Len() > 0 { + apiObject.OKActions = flex.ExpandStringValueSet(v.(*schema.Set)) } return apiObject } -func flattenActionsSuppressor(alarm *cloudwatch.CompositeAlarm) map[string]interface{} { - actionsSuppressor := map[string]interface{}{ - "alarm": aws.StringValue(alarm.ActionsSuppressor), - "extension_period": aws.Int64Value(alarm.ActionsSuppressorExtensionPeriod), - "wait_period": aws.Int64Value(alarm.ActionsSuppressorWaitPeriod), +func flattenActionsSuppressor(apiObject *types.CompositeAlarm) map[string]interface{} { + if apiObject == nil || apiObject.ActionsSuppressor == nil { + return nil + } + + tfMap := map[string]interface{}{ + "alarm": aws.ToString(apiObject.ActionsSuppressor), + "extension_period": aws.ToInt32(apiObject.ActionsSuppressorExtensionPeriod), + "wait_period": aws.ToInt32(apiObject.ActionsSuppressorWaitPeriod), } - return actionsSuppressor + + return tfMap } -func expandActionsSuppressor(v []interface{}) *cloudwatch.CompositeAlarm { - if v[0] == nil { +func expandActionsSuppressor(tfMap map[string]interface{}) *types.CompositeAlarm { + if tfMap == nil { return nil } - alarmResource := v[0].(map[string]interface{}) - alarm := cloudwatch.CompositeAlarm{} + apiObject := &types.CompositeAlarm{} - if v, ok := alarmResource["alarm"]; ok && v.(string) != "" { - alarm.ActionsSuppressor = aws.String(v.(string)) + if v, ok := tfMap["alarm"]; ok && v.(string) != "" { + apiObject.ActionsSuppressor = aws.String(v.(string)) } - if v, ok := alarmResource["extension_period"]; ok { - alarm.ActionsSuppressorExtensionPeriod = aws.Int64(int64(v.(int))) + + if v, ok := tfMap["extension_period"]; ok { + apiObject.ActionsSuppressorExtensionPeriod = aws.Int32(int32(v.(int))) } - if v, ok := alarmResource["wait_period"]; ok { - alarm.ActionsSuppressorWaitPeriod = aws.Int64(int64(v.(int))) + + if v, ok := tfMap["wait_period"]; ok { + apiObject.ActionsSuppressorWaitPeriod = aws.Int32(int32(v.(int))) } - return &alarm + return apiObject } diff --git a/internal/service/cloudwatch/exports_test.go b/internal/service/cloudwatch/exports_test.go index 3283c282f92..d3ef310366e 100644 --- a/internal/service/cloudwatch/exports_test.go +++ b/internal/service/cloudwatch/exports_test.go @@ -5,11 +5,13 @@ package cloudwatch // Exports for use in tests only. var ( - ResourceDashboard = resourceDashboard - ResourceMetricAlarm = resourceMetricAlarm - ResourceMetricStream = resourceMetricStream + ResourceCompositeAlarm = resourceCompositeAlarm + ResourceDashboard = resourceDashboard + ResourceMetricAlarm = resourceMetricAlarm + ResourceMetricStream = resourceMetricStream - FindDashboardByName = findDashboardByName - FindMetricAlarmByName = findMetricAlarmByName - FindMetricStreamByName = findMetricStreamByName + FindCompositeAlarmByName = findCompositeAlarmByName + FindDashboardByName = findDashboardByName + FindMetricAlarmByName = findMetricAlarmByName + FindMetricStreamByName = findMetricStreamByName ) diff --git a/internal/service/cloudwatch/service_package_gen.go b/internal/service/cloudwatch/service_package_gen.go index f0db2748b5e..e8b4d71ef5e 100644 --- a/internal/service/cloudwatch/service_package_gen.go +++ b/internal/service/cloudwatch/service_package_gen.go @@ -29,7 +29,7 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ { - Factory: ResourceCompositeAlarm, + Factory: resourceCompositeAlarm, TypeName: "aws_cloudwatch_composite_alarm", Name: "Composite Alarm", Tags: &types.ServicePackageResourceTags{ From b0b196d96688aeadbf69e69916f8df13c3b54211 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 30 Jan 2024 14:07:03 -0500 Subject: [PATCH 08/12] cloudwatch: Migrate sweepers to AWS SDK for Go v2. --- internal/service/cloudwatch/sweep.go | 173 ++++++++++++++++++++++++--- 1 file changed, 156 insertions(+), 17 deletions(-) diff --git a/internal/service/cloudwatch/sweep.go b/internal/service/cloudwatch/sweep.go index c04a006e80f..ab42846a54c 100644 --- a/internal/service/cloudwatch/sweep.go +++ b/internal/service/cloudwatch/sweep.go @@ -7,11 +7,13 @@ import ( "fmt" "log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/internal/sweep" - "github.com/hashicorp/terraform-provider-aws/internal/sweep/awsv1" + "github.com/hashicorp/terraform-provider-aws/internal/sweep/awsv2" ) func RegisterSweepers() { @@ -19,6 +21,21 @@ func RegisterSweepers() { Name: "aws_cloudwatch_composite_alarm", F: sweepCompositeAlarms, }) + + resource.AddTestSweepers("aws_cloudwatch_dashboard", &resource.Sweeper{ + Name: "aws_cloudwatch_metric_stream", + F: sweepDashboards, + }) + + resource.AddTestSweepers("aws_cloudwatch_metric_alarm", &resource.Sweeper{ + Name: "aws_cloudwatch_metric_alarm", + F: sweepMetricAlarms, + }) + + resource.AddTestSweepers("aws_cloudwatch_metric_stream", &resource.Sweeper{ + Name: "aws_cloudwatch_metric_stream", + F: sweepMetricStreams, + }) } func sweepCompositeAlarms(region string) error { @@ -27,41 +44,163 @@ func sweepCompositeAlarms(region string) error { if err != nil { return fmt.Errorf("error getting client: %w", err) } - conn := client.CloudWatchConn(ctx) + conn := client.CloudWatchClient(ctx) input := &cloudwatch.DescribeAlarmsInput{ - AlarmTypes: aws.StringSlice([]string{cloudwatch.AlarmTypeCompositeAlarm}), + AlarmTypes: tfslices.Of(types.AlarmTypeCompositeAlarm), } sweepResources := make([]sweep.Sweepable, 0) - err = conn.DescribeAlarmsPagesWithContext(ctx, input, func(page *cloudwatch.DescribeAlarmsOutput, lastPage bool) bool { - if page == nil { - return !lastPage + pages := cloudwatch.NewDescribeAlarmsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] SkippingCloudWatch Composite Alarm sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing CloudWatch Composite Alarms (%s): %w", region, err) } for _, v := range page.CompositeAlarms { - r := ResourceCompositeAlarm() + r := resourceCompositeAlarm() d := r.Data(nil) - d.SetId(aws.StringValue(v.AlarmName)) + d.SetId(aws.ToString(v.AlarmName)) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } + } - return !lastPage - }) + err = sweep.SweepOrchestrator(ctx, sweepResources) - if awsv1.SkipSweepError(err) { - log.Printf("[WARN] SkippingCloudWatch Composite Alarm sweep for %s: %s", region, err) - return nil + if err != nil { + return fmt.Errorf("error sweeping CloudWatch Composite Alarms (%s): %w", region, err) } + return nil +} + +func sweepDashboards(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) if err != nil { - return fmt.Errorf("error listing CloudWatch Composite Alarms (%s): %w", region, err) + return fmt.Errorf("error getting client: %w", err) + } + conn := client.CloudWatchClient(ctx) + input := &cloudwatch.ListDashboardsInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := cloudwatch.NewListDashboardsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] SkippingCloudWatch Dashboard sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing CloudWatch Dashboards (%s): %w", region, err) + } + + for _, v := range page.DashboardEntries { + r := resourceDashboard() + d := r.Data(nil) + d.SetId(aws.ToString(v.DashboardName)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } } err = sweep.SweepOrchestrator(ctx, sweepResources) if err != nil { - return fmt.Errorf("error sweeping CloudWatch Composite Alarms (%s): %w", region, err) + return fmt.Errorf("error sweeping CloudWatch Dashboards (%s): %w", region, err) + } + + return nil +} + +func sweepMetricAlarms(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.CloudWatchClient(ctx) + input := &cloudwatch.DescribeAlarmsInput{ + AlarmTypes: tfslices.Of(types.AlarmTypeMetricAlarm), + } + sweepResources := make([]sweep.Sweepable, 0) + + pages := cloudwatch.NewDescribeAlarmsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] SkippingCloudWatch Metric Alarm sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing CloudWatch Metric Alarms (%s): %w", region, err) + } + + for _, v := range page.MetricAlarms { + r := resourceMetricAlarm() + d := r.Data(nil) + d.SetId(aws.ToString(v.AlarmName)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping CloudWatch Metric Alarms (%s): %w", region, err) + } + + return nil +} + +func sweepMetricStreams(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.CloudWatchClient(ctx) + input := &cloudwatch.ListMetricStreamsInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := cloudwatch.NewListMetricStreamsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] SkippingCloudWatch Metric Stream sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing CloudWatch Metric Streams (%s): %w", region, err) + } + + for _, v := range page.Entries { + r := resourceMetricStream() + d := r.Data(nil) + d.SetId(aws.ToString(v.Name)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping CloudWatch Metric Streams (%s): %w", region, err) } return nil From 241ff071cbb3019d6b1df2010cc5f545c2666cd6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 30 Jan 2024 14:32:04 -0500 Subject: [PATCH 09/12] cloudwatch: Migrate acceptance tests to AWS SDK for Go v2. --- .../cloudwatch/composite_alarm_test.go | 32 ++- internal/service/cloudwatch/dashboard_test.go | 184 +++--------------- .../service/cloudwatch/metric_alarm_test.go | 59 +++--- .../service/cloudwatch/metric_stream_test.go | 37 ++-- names/names.go | 1 + 5 files changed, 92 insertions(+), 221 deletions(-) diff --git a/internal/service/cloudwatch/composite_alarm_test.go b/internal/service/cloudwatch/composite_alarm_test.go index 39638c29f1c..0adf652a0b8 100644 --- a/internal/service/cloudwatch/composite_alarm_test.go +++ b/internal/service/cloudwatch/composite_alarm_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/service/cloudwatch" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -17,6 +16,7 @@ import ( "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" + "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccCloudWatchCompositeAlarm_basic(t *testing.T) { @@ -26,7 +26,7 @@ func TestAccCloudWatchCompositeAlarm_basic(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCompositeAlarmDestroy(ctx), Steps: []resource.TestStep{ @@ -62,7 +62,7 @@ func TestAccCloudWatchCompositeAlarm_disappears(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCompositeAlarmDestroy(ctx), Steps: []resource.TestStep{ @@ -85,7 +85,7 @@ func TestAccCloudWatchCompositeAlarm_tags(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCompositeAlarmDestroy(ctx), Steps: []resource.TestStep{ @@ -130,7 +130,7 @@ func TestAccCloudWatchCompositeAlarm_actionsEnabled(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCompositeAlarmDestroy(ctx), Steps: []resource.TestStep{ @@ -164,7 +164,7 @@ func TestAccCloudWatchCompositeAlarm_alarmActions(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCompositeAlarmDestroy(ctx), Steps: []resource.TestStep{ @@ -205,7 +205,7 @@ func TestAccCloudWatchCompositeAlarm_description(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCompositeAlarmDestroy(ctx), Steps: []resource.TestStep{ @@ -239,7 +239,7 @@ func TestAccCloudWatchCompositeAlarm_updateAlarmRule(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCompositeAlarmDestroy(ctx), Steps: []resource.TestStep{ @@ -272,7 +272,7 @@ func TestAccCloudWatchCompositeAlarm_insufficientDataActions(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCompositeAlarmDestroy(ctx), Steps: []resource.TestStep{ @@ -313,7 +313,7 @@ func TestAccCloudWatchCompositeAlarm_okActions(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCompositeAlarmDestroy(ctx), Steps: []resource.TestStep{ @@ -359,7 +359,7 @@ func TestAccCloudWatchCompositeAlarm_allActions(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCompositeAlarmDestroy(ctx), Steps: []resource.TestStep{ @@ -397,7 +397,7 @@ func TestAccCloudWatchCompositeAlarm_actionsSuppressor(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckCompositeAlarmDestroy(ctx), Steps: []resource.TestStep{ @@ -422,7 +422,7 @@ func TestAccCloudWatchCompositeAlarm_actionsSuppressor(t *testing.T) { func testAccCheckCompositeAlarmDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_cloudwatch_composite_alarm" { @@ -453,11 +453,7 @@ func testAccCheckCompositeAlarmExists(ctx context.Context, n string) resource.Te return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No CloudWatch Composite Alarm ID is set") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchClient(ctx) _, err := tfcloudwatch.FindCompositeAlarmByName(ctx, conn, rs.Primary.ID) diff --git a/internal/service/cloudwatch/dashboard_test.go b/internal/service/cloudwatch/dashboard_test.go index f450a278362..a9ff18c2511 100644 --- a/internal/service/cloudwatch/dashboard_test.go +++ b/internal/service/cloudwatch/dashboard_test.go @@ -5,39 +5,37 @@ package cloudwatch_test import ( "context" - "encoding/json" "fmt" - "reflect" - "strings" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/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" + "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccCloudWatchDashboard_basic(t *testing.T) { ctx := acctest.Context(t) var dashboard cloudwatch.GetDashboardOutput resourceName := "aws_cloudwatch_dashboard.test" - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckDashboardDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccDashboardConfig_basic(rInt), + Config: testAccDashboardConfig_basic(rName, basicWidget), Check: resource.ComposeTestCheckFunc( testAccCheckDashboardExists(ctx, resourceName, &dashboard), - resource.TestCheckResourceAttr(resourceName, "dashboard_name", testAccDashboardName(rInt)), + resource.TestCheckResourceAttrSet(resourceName, "dashboard_arn"), ), }, { @@ -45,98 +43,57 @@ func TestAccCloudWatchDashboard_basic(t *testing.T) { ImportState: true, ImportStateVerify: true, }, - }, - }) -} - -func TestAccCloudWatchDashboard_update(t *testing.T) { - ctx := acctest.Context(t) - var dashboard cloudwatch.GetDashboardOutput - resourceName := "aws_cloudwatch_dashboard.test" - rInt := sdkacctest.RandInt() - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckDashboardDestroy(ctx), - Steps: []resource.TestStep{ { - Config: testAccDashboardConfig_basic(rInt), + Config: testAccDashboardConfig_basic(rName, updatedWidget), Check: resource.ComposeTestCheckFunc( testAccCheckDashboardExists(ctx, resourceName, &dashboard), - testAccCheckDashboardBodyIsExpected(ctx, resourceName, basicWidget), - resource.TestCheckResourceAttr(resourceName, "dashboard_name", testAccDashboardName(rInt)), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccDashboardConfig_updateBody(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckDashboardExists(ctx, resourceName, &dashboard), - testAccCheckDashboardBodyIsExpected(ctx, resourceName, updatedWidget), - resource.TestCheckResourceAttr(resourceName, "dashboard_name", testAccDashboardName(rInt)), + resource.TestCheckResourceAttrSet(resourceName, "dashboard_arn"), ), }, }, }) } -func TestAccCloudWatchDashboard_updateName(t *testing.T) { +func TestAccCloudWatchDashboard_disappears(t *testing.T) { ctx := acctest.Context(t) var dashboard cloudwatch.GetDashboardOutput resourceName := "aws_cloudwatch_dashboard.test" - rInt := sdkacctest.RandInt() - rInt2 := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, cloudwatch.EndpointsID), + ErrorCheck: acctest.ErrorCheck(t, names.CloudWatchEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckDashboardDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccDashboardConfig_basic(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckDashboardExists(ctx, resourceName, &dashboard), - testAccCheckDashboardBodyIsExpected(ctx, resourceName, basicWidget), - resource.TestCheckResourceAttr(resourceName, "dashboard_name", testAccDashboardName(rInt)), - ), - }, - { - Config: testAccDashboardConfig_basic(rInt2), + Config: testAccDashboardConfig_basic(rName, basicWidget), Check: resource.ComposeTestCheckFunc( testAccCheckDashboardExists(ctx, resourceName, &dashboard), - testAccCheckDashboardBodyIsExpected(ctx, resourceName, basicWidget), - resource.TestCheckResourceAttr(resourceName, "dashboard_name", testAccDashboardName(rInt2)), - testAccCheckDashboardDestroyPrevious(ctx, testAccDashboardName(rInt)), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfcloudwatch.ResourceDashboard(), resourceName), ), + ExpectNonEmptyPlan: true, }, }, }) } -func testAccCheckDashboardExists(ctx context.Context, n string, dashboard *cloudwatch.GetDashboardOutput) resource.TestCheckFunc { +func testAccCheckDashboardExists(ctx context.Context, n string, v *cloudwatch.GetDashboardOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchConn(ctx) - params := cloudwatch.GetDashboardInput{ - DashboardName: aws.String(rs.Primary.ID), - } + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchClient(ctx) + + output, err := tfcloudwatch.FindDashboardByName(ctx, conn, rs.Primary.ID) - resp, err := conn.GetDashboardWithContext(ctx, ¶ms) if err != nil { return err } - *dashboard = *resp + *v = *output return nil } @@ -144,46 +101,24 @@ func testAccCheckDashboardExists(ctx context.Context, n string, dashboard *cloud func testAccCheckDashboardDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_cloudwatch_dashboard" { continue } - params := cloudwatch.GetDashboardInput{ - DashboardName: aws.String(rs.Primary.ID), - } + _, err := tfcloudwatch.FindDashboardByName(ctx, conn, rs.Primary.ID) - _, err := conn.GetDashboardWithContext(ctx, ¶ms) - if err == nil { - return fmt.Errorf("Dashboard still exists: %s", rs.Primary.ID) + if tfresource.NotFound(err) { + continue } - if !tfcloudwatch.IsDashboardNotFoundErr(err) { + + if err != nil { return err } - } - - return nil - } -} -func testAccCheckDashboardDestroyPrevious(ctx context.Context, dashboardName string) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchConn(ctx) - - params := cloudwatch.GetDashboardInput{ - DashboardName: aws.String(dashboardName), - } - - _, err := conn.GetDashboardWithContext(ctx, ¶ms) - - if err == nil { - return fmt.Errorf("Dashboard still exists: %s", dashboardName) - } - - if !tfcloudwatch.IsDashboardNotFoundErr(err) { - return err + return fmt.Errorf("CloudWatch Dashboard %s still exists", rs.Primary.ID) } return nil @@ -222,69 +157,14 @@ const ( }` ) -func testAccDashboardName(rInt int) string { - return fmt.Sprintf("terraform-test-dashboard-%d", rInt) -} - -func testAccDashboardConfig_basic(rInt int) string { +func testAccDashboardConfig_basic(rName, body string) string { return fmt.Sprintf(` resource "aws_cloudwatch_dashboard" "test" { - dashboard_name = "terraform-test-dashboard-%d" + dashboard_name = %[1]q dashboard_body = < Date: Tue, 30 Jan 2024 16:26:56 -0500 Subject: [PATCH 10/12] cloudwatch: Fixes from testing. --- internal/service/cloudwatch/consts.go | 4 -- internal/service/cloudwatch/dashboard.go | 5 +-- internal/service/cloudwatch/errors.go | 12 ++++++ .../service/cloudwatch/metric_alarm_test.go | 42 +++++++++++-------- internal/service/cloudwatch/metric_stream.go | 2 +- 5 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 internal/service/cloudwatch/errors.go diff --git a/internal/service/cloudwatch/consts.go b/internal/service/cloudwatch/consts.go index a2ffad3a878..fe66fb623ba 100644 --- a/internal/service/cloudwatch/consts.go +++ b/internal/service/cloudwatch/consts.go @@ -3,10 +3,6 @@ package cloudwatch -const ( - ResNameDashboard = "Dashboard" -) - const ( lowSampleCountPercentilesEvaluate = "evaluate" lowSampleCountPercentilesmissingDataIgnore = "ignore" diff --git a/internal/service/cloudwatch/dashboard.go b/internal/service/cloudwatch/dashboard.go index 8fa5e32fdfa..f20745d6f1a 100644 --- a/internal/service/cloudwatch/dashboard.go +++ b/internal/service/cloudwatch/dashboard.go @@ -9,14 +9,13 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch" - "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -134,7 +133,7 @@ func findDashboardByName(ctx context.Context, conn *cloudwatch.Client, name stri output, err := conn.GetDashboard(ctx, input) - if errs.IsA[*types.ResourceNotFound](err) { + if tfawserr.ErrCodeEquals(err, errCodeResourceNotFound) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, diff --git a/internal/service/cloudwatch/errors.go b/internal/service/cloudwatch/errors.go new file mode 100644 index 00000000000..1a3aa562154 --- /dev/null +++ b/internal/service/cloudwatch/errors.go @@ -0,0 +1,12 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cloudwatch + +import ( + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" +) + +var ( + errCodeResourceNotFound = (*types.ResourceNotFound)(nil).ErrorCode() +) diff --git a/internal/service/cloudwatch/metric_alarm_test.go b/internal/service/cloudwatch/metric_alarm_test.go index adeb092b02d..5235345a313 100755 --- a/internal/service/cloudwatch/metric_alarm_test.go +++ b/internal/service/cloudwatch/metric_alarm_test.go @@ -446,9 +446,10 @@ func TestAccCloudWatchMetricAlarm_metricQuery(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metric_query"}, }, { Config: testAccMetricAlarmConfig_metricQueryExpressionReference(rName), @@ -477,9 +478,10 @@ func TestAccCloudWatchMetricAlarm_metricQuery(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metric_query"}, }, { Config: testAccMetricAlarmConfig_metricQueryCrossAccount(rName), @@ -492,9 +494,10 @@ func TestAccCloudWatchMetricAlarm_metricQuery(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metric_query"}, }, { Config: testAccMetricAlarmConfig_metricQueryExpressionReferenceUpdated(rName), @@ -518,9 +521,10 @@ func TestAccCloudWatchMetricAlarm_metricQuery(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metric_query"}, }, { Config: testAccMetricAlarmConfig_metricQueryExpressionReference(rName), @@ -530,9 +534,10 @@ func TestAccCloudWatchMetricAlarm_metricQuery(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metric_query"}, }, { Config: testAccMetricAlarmConfig_anomalyDetectionExpression(rName), @@ -549,9 +554,10 @@ func TestAccCloudWatchMetricAlarm_metricQuery(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metric_query"}, }, }, }) diff --git a/internal/service/cloudwatch/metric_stream.go b/internal/service/cloudwatch/metric_stream.go index ce0d998ce3d..0808694f173 100644 --- a/internal/service/cloudwatch/metric_stream.go +++ b/internal/service/cloudwatch/metric_stream.go @@ -373,7 +373,7 @@ func findMetricStreamByName(ctx context.Context, conn *cloudwatch.Client, name s output, err := conn.GetMetricStream(ctx, input) - if errs.IsA[*types.ResourceNotFound](err) { + if errs.IsA[*types.ResourceNotFoundException](err) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, From 882ec2f77884648a9c1b8fd8697b53599fed4f04 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 30 Jan 2024 16:30:00 -0500 Subject: [PATCH 11/12] Correct CHANGELOG entry file name. --- .changelog/{#####.txt => 35569.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changelog/{#####.txt => 35569.txt} (100%) diff --git a/.changelog/#####.txt b/.changelog/35569.txt similarity index 100% rename from .changelog/#####.txt rename to .changelog/35569.txt From 4c16b560c225008a644f225c4cc9e679f64e76da Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 31 Jan 2024 09:26:59 -0500 Subject: [PATCH 12/12] Run 'go generate ./internal/generate/serviceendpointtests'. --- .../cloudwatch/service_endpoints_gen_test.go | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/internal/service/cloudwatch/service_endpoints_gen_test.go b/internal/service/cloudwatch/service_endpoints_gen_test.go index 3cde08095e0..526850e2419 100644 --- a/internal/service/cloudwatch/service_endpoints_gen_test.go +++ b/internal/service/cloudwatch/service_endpoints_gen_test.go @@ -4,16 +4,16 @@ package cloudwatch_test import ( "context" + "errors" "fmt" - "net/url" "os" "path/filepath" "reflect" "strings" "testing" - "github.com/aws/aws-sdk-go/aws/endpoints" - cloudwatch_sdkv1 "github.com/aws/aws-sdk-go/service/cloudwatch" + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + cloudwatch_sdkv2 "github.com/aws/aws-sdk-go-v2/service/cloudwatch" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/google/go-cmp/cmp" @@ -212,32 +212,42 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S } func defaultEndpoint(region string) string { - r := endpoints.DefaultResolver() + r := cloudwatch_sdkv2.NewDefaultEndpointResolverV2() - ep, err := r.EndpointFor(cloudwatch_sdkv1.EndpointsID, region) + ep, err := r.ResolveEndpoint(context.Background(), cloudwatch_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) if err != nil { return err.Error() } - url, _ := url.Parse(ep.URL) - - if url.Path == "" { - url.Path = "/" + if ep.URI.Path == "" { + ep.URI.Path = "/" } - return url.String() + return ep.URI.String() } func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { t.Helper() - client := meta.CloudWatchConn(ctx) - - req, _ := client.ListDashboardsRequest(&cloudwatch_sdkv1.ListDashboardsInput{}) + var endpoint string - req.HTTPRequest.URL.Path = "/" + client := meta.CloudWatchClient(ctx) - endpoint := req.HTTPRequest.URL.String() + _, err := client.ListDashboards(ctx, &cloudwatch_sdkv2.ListDashboardsInput{}, + func(opts *cloudwatch_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &endpoint), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } return endpoint }