From 667e7c2e05e6ed6acabe772009f7446678db6f50 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sat, 11 May 2024 16:45:00 -0400 Subject: [PATCH 01/34] ssm: Only 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 afb9ba73024..9b97df0dffe 100644 --- a/names/data/names_data.csv +++ b/names/data/names_data.csv @@ -347,7 +347,7 @@ snow-device-management,snowdevicemanagement,snowdevicemanagement,snowdevicemanag snowball,snowball,snowball,snowball,,snowball,,,Snowball,Snowball,,1,,,aws_snowball_,,snowball_,Snow Family,AWS,,x,,,,,Snowball,,, sns,sns,sns,sns,,sns,,,SNS,SNS,,,2,,aws_sns_,,sns_,SNS (Simple Notification),Amazon,,,,,,,SNS,ListSubscriptions,, sqs,sqs,sqs,sqs,,sqs,,,SQS,SQS,,,2,,aws_sqs_,,sqs_,SQS (Simple Queue),Amazon,,,,,,,SQS,ListQueues,, -ssm,ssm,ssm,ssm,,ssm,,,SSM,SSM,,1,2,,aws_ssm_,,ssm_,SSM (Systems Manager),AWS,,,,,,,SSM,ListDocuments,, +ssm,ssm,ssm,ssm,,ssm,,,SSM,SSM,,,2,,aws_ssm_,,ssm_,SSM (Systems Manager),AWS,,,,,,,SSM,ListDocuments,, ssm-contacts,ssmcontacts,ssmcontacts,ssmcontacts,,ssmcontacts,,,SSMContacts,SSMContacts,,,2,,aws_ssmcontacts_,,ssmcontacts_,SSM Contacts,AWS,,,,,,,SSM Contacts,ListContacts,, ssm-incidents,ssmincidents,ssmincidents,ssmincidents,,ssmincidents,,,SSMIncidents,SSMIncidents,,,2,,aws_ssmincidents_,,ssmincidents_,SSM Incident Manager Incidents,AWS,,,,,,,SSM Incidents,ListResponsePlans,, ssm-sap,ssmsap,ssmsap,ssmsap,,ssmsap,,,SSMSAP,SsmSap,,,2,,aws_ssmsap_,,ssmsap_,Systems Manager for SAP,AWS,,,,,,,Ssm Sap,ListApplications,, From 25925b34d362892f7670d128fce99fc46242f56d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sat, 11 May 2024 16:46:22 -0400 Subject: [PATCH 02/34] ssm: Generate AWS SDK for Go v2 tagging code. --- internal/service/ssm/generate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/ssm/generate.go b/internal/service/ssm/generate.go index bb8460a6a78..f40af846b28 100644 --- a/internal/service/ssm/generate.go +++ b/internal/service/ssm/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=ResourceId -ListTagsOutTagsElem=TagList -ServiceTagsSlice -TagOp=AddTagsToResource -TagInIDElem=ResourceId -TagResTypeElem=ResourceType -UntagOp=RemoveTagsFromResource -UpdateTags -CreateTags +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -ListTags -ListTagsInIDElem=ResourceId -ListTagsOutTagsElem=TagList -ServiceTagsSlice -TagOp=AddTagsToResource -TagInIDElem=ResourceId -TagResTypeElem=ResourceType -UntagOp=RemoveTagsFromResource -UpdateTags -CreateTags //go:generate go run ../../generate/servicepackage/main.go // ONLY generate directives and package declaration! Do not add anything else to this file. From 41727fe2a16e1af153dc1d18383c1fe8376955fc Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sat, 11 May 2024 16:47:47 -0400 Subject: [PATCH 03/34] Run 'make gen'. --- internal/conns/awsclient_gen.go | 5 --- .../service/ssm/service_endpoints_gen_test.go | 41 ++++--------------- internal/service/ssm/service_package_gen.go | 10 ----- internal/service/ssm/tags_gen.go | 38 ++++++++--------- 4 files changed, 26 insertions(+), 68 deletions(-) diff --git a/internal/conns/awsclient_gen.go b/internal/conns/awsclient_gen.go index ede8ee662b3..b4e053df328 100644 --- a/internal/conns/awsclient_gen.go +++ b/internal/conns/awsclient_gen.go @@ -244,7 +244,6 @@ import ( ses_sdkv1 "github.com/aws/aws-sdk-go/service/ses" sfn_sdkv1 "github.com/aws/aws-sdk-go/service/sfn" simpledb_sdkv1 "github.com/aws/aws-sdk-go/service/simpledb" - ssm_sdkv1 "github.com/aws/aws-sdk-go/service/ssm" storagegateway_sdkv1 "github.com/aws/aws-sdk-go/service/storagegateway" transfer_sdkv1 "github.com/aws/aws-sdk-go/service/transfer" waf_sdkv1 "github.com/aws/aws-sdk-go/service/waf" @@ -1082,10 +1081,6 @@ func (c *AWSClient) SQSClient(ctx context.Context) *sqs_sdkv2.Client { return errs.Must(client[*sqs_sdkv2.Client](ctx, c, names.SQS, make(map[string]any))) } -func (c *AWSClient) SSMConn(ctx context.Context) *ssm_sdkv1.SSM { - return errs.Must(conn[*ssm_sdkv1.SSM](ctx, c, names.SSM, make(map[string]any))) -} - func (c *AWSClient) SSMClient(ctx context.Context) *ssm_sdkv2.Client { return errs.Must(client[*ssm_sdkv2.Client](ctx, c, names.SSM, make(map[string]any))) } diff --git a/internal/service/ssm/service_endpoints_gen_test.go b/internal/service/ssm/service_endpoints_gen_test.go index dd307b74442..69ad803fd55 100644 --- a/internal/service/ssm/service_endpoints_gen_test.go +++ b/internal/service/ssm/service_endpoints_gen_test.go @@ -15,7 +15,6 @@ import ( aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" ssm_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ssm" - ssm_sdkv1 "github.com/aws/aws-sdk-go/service/ssm" "github.com/aws/smithy-go/middleware" smithyhttp "github.com/aws/smithy-go/transport/http" "github.com/google/go-cmp/cmp" @@ -204,25 +203,13 @@ func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.S }, } - t.Run("v1", func(t *testing.T) { - for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv - testcase := testcase + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase - t.Run(name, func(t *testing.T) { - testEndpointCase(t, region, testcase, callServiceV1) - }) - } - }) - - t.Run("v2", func(t *testing.T) { - for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv - testcase := testcase - - t.Run(name, func(t *testing.T) { - testEndpointCase(t, region, testcase, callServiceV2) - }) - } - }) + t.Run(name, func(t *testing.T) { + testEndpointCase(t, region, testcase, callService) + }) + } } func defaultEndpoint(region string) string { @@ -242,7 +229,7 @@ func defaultEndpoint(region string) string { return ep.URI.String() } -func callServiceV2(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { t.Helper() var endpoint string @@ -266,20 +253,6 @@ func callServiceV2(ctx context.Context, t *testing.T, meta *conns.AWSClient) str return endpoint } -func callServiceV1(ctx context.Context, t *testing.T, meta *conns.AWSClient) string { - t.Helper() - - client := meta.SSMConn(ctx) - - req, _ := client.ListDocumentsRequest(&ssm_sdkv1.ListDocumentsInput{}) - - req.HTTPRequest.URL.Path = "/" - - endpoint := req.HTTPRequest.URL.String() - - return endpoint -} - func withNoConfig(_ *caseSetup) { // no-op } diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index c57d9aee2b2..348f2253007 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -7,9 +7,6 @@ import ( aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" ssm_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ssm" - aws_sdkv1 "github.com/aws/aws-sdk-go/aws" - session_sdkv1 "github.com/aws/aws-sdk-go/aws/session" - ssm_sdkv1 "github.com/aws/aws-sdk-go/service/ssm" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/types" "github.com/hashicorp/terraform-provider-aws/names" @@ -134,13 +131,6 @@ func (p *servicePackage) ServicePackageName() string { return names.SSM } -// 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) (*ssm_sdkv1.SSM, error) { - sess := config[names.AttrSession].(*session_sdkv1.Session) - - return ssm_sdkv1.New(sess.Copy(&aws_sdkv1.Config{Endpoint: aws_sdkv1.String(config[names.AttrEndpoint].(string))})), nil -} - // 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) (*ssm_sdkv2.Client, error) { cfg := *(config["aws_sdkv2_config"].(*aws_sdkv2.Config)) diff --git a/internal/service/ssm/tags_gen.go b/internal/service/ssm/tags_gen.go index bb532d2fdb8..39f2235eab5 100644 --- a/internal/service/ssm/tags_gen.go +++ b/internal/service/ssm/tags_gen.go @@ -5,9 +5,9 @@ import ( "context" "fmt" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "github.com/aws/aws-sdk-go/service/ssm/ssmiface" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/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,13 +19,13 @@ import ( // listTags lists ssm 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 ssmiface.SSMAPI, identifier, resourceType string) (tftags.KeyValueTags, error) { +func listTags(ctx context.Context, conn *ssm.Client, identifier, resourceType string, optFns ...func(*ssm.Options)) (tftags.KeyValueTags, error) { input := &ssm.ListTagsForResourceInput{ ResourceId: aws.String(identifier), ResourceType: aws.String(resourceType), } - output, err := conn.ListTagsForResourceWithContext(ctx, input) + output, err := conn.ListTagsForResource(ctx, input, optFns...) if err != nil { return tftags.New(ctx, nil), err @@ -37,7 +37,7 @@ func listTags(ctx context.Context, conn ssmiface.SSMAPI, identifier, resourceTyp // ListTags lists ssm service tags and set them in Context. // It is called from outside this package. func (p *servicePackage) ListTags(ctx context.Context, meta any, identifier, resourceType string) error { - tags, err := listTags(ctx, meta.(*conns.AWSClient).SSMConn(ctx), identifier, resourceType) + tags, err := listTags(ctx, meta.(*conns.AWSClient).SSMClient(ctx), identifier, resourceType) if err != nil { return err @@ -53,11 +53,11 @@ func (p *servicePackage) ListTags(ctx context.Context, meta any, identifier, res // []*SERVICE.Tag handling // Tags returns ssm service tags. -func Tags(tags tftags.KeyValueTags) []*ssm.Tag { - result := make([]*ssm.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 := &ssm.Tag{ + tag := awstypes.Tag{ Key: aws.String(k), Value: aws.String(v), } @@ -69,11 +69,11 @@ func Tags(tags tftags.KeyValueTags) []*ssm.Tag { } // KeyValueTags creates tftags.KeyValueTags from ssm service tags. -func KeyValueTags(ctx context.Context, tags []*ssm.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) @@ -81,7 +81,7 @@ func KeyValueTags(ctx context.Context, tags []*ssm.Tag) tftags.KeyValueTags { // getTagsIn returns ssm service tags from Context. // nil is returned if there are no input tags. -func getTagsIn(ctx context.Context) []*ssm.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 @@ -92,14 +92,14 @@ func getTagsIn(ctx context.Context) []*ssm.Tag { } // setTagsOut sets ssm service tags in Context. -func setTagsOut(ctx context.Context, tags []*ssm.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 ssm service tags for new resources. -func createTags(ctx context.Context, conn ssmiface.SSMAPI, identifier, resourceType string, tags []*ssm.Tag) error { +func createTags(ctx context.Context, conn *ssm.Client, identifier, resourceType string, tags []awstypes.Tag) error { if len(tags) == 0 { return nil } @@ -110,7 +110,7 @@ func createTags(ctx context.Context, conn ssmiface.SSMAPI, identifier, resourceT // updateTags updates ssm 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 ssmiface.SSMAPI, identifier, resourceType string, oldTagsMap, newTagsMap any) error { +func updateTags(ctx context.Context, conn *ssm.Client, identifier, resourceType string, oldTagsMap, newTagsMap any, optFns ...func(*ssm.Options)) error { oldTags := tftags.New(ctx, oldTagsMap) newTags := tftags.New(ctx, newTagsMap) @@ -122,10 +122,10 @@ func updateTags(ctx context.Context, conn ssmiface.SSMAPI, identifier, resourceT input := &ssm.RemoveTagsFromResourceInput{ ResourceId: aws.String(identifier), ResourceType: aws.String(resourceType), - TagKeys: aws.StringSlice(removedTags.Keys()), + TagKeys: removedTags.Keys(), } - _, err := conn.RemoveTagsFromResourceWithContext(ctx, input) + _, err := conn.RemoveTagsFromResource(ctx, input, optFns...) if err != nil { return fmt.Errorf("untagging resource (%s): %w", identifier, err) @@ -141,7 +141,7 @@ func updateTags(ctx context.Context, conn ssmiface.SSMAPI, identifier, resourceT Tags: Tags(updatedTags), } - _, err := conn.AddTagsToResourceWithContext(ctx, input) + _, err := conn.AddTagsToResource(ctx, input, optFns...) if err != nil { return fmt.Errorf("tagging resource (%s): %w", identifier, err) @@ -154,5 +154,5 @@ func updateTags(ctx context.Context, conn ssmiface.SSMAPI, identifier, resourceT // UpdateTags updates ssm service tags. // It is called from outside this package. func (p *servicePackage) UpdateTags(ctx context.Context, meta any, identifier, resourceType string, oldTags, newTags any) error { - return updateTags(ctx, meta.(*conns.AWSClient).SSMConn(ctx), identifier, resourceType, oldTags, newTags) + return updateTags(ctx, meta.(*conns.AWSClient).SSMClient(ctx), identifier, resourceType, oldTags, newTags) } From 94058db99eaff8fdc45e0a1f520f3640875a5c73 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sat, 11 May 2024 16:59:35 -0400 Subject: [PATCH 04/34] r/aws_ssm_activation: Migrate to AWS SDK for Go v2. --- internal/service/ssm/activation.go | 78 ++++++++++----------- internal/service/ssm/activation_test.go | 20 +++--- internal/service/ssm/errors.go | 8 +++ internal/service/ssm/exports_test.go | 2 + internal/service/ssm/service_package_gen.go | 2 +- 5 files changed, 56 insertions(+), 54 deletions(-) create mode 100644 internal/service/ssm/errors.go diff --git a/internal/service/ssm/activation.go b/internal/service/ssm/activation.go index d6bd8095850..46f37991ec6 100644 --- a/internal/service/ssm/activation.go +++ b/internal/service/ssm/activation.go @@ -8,13 +8,14 @@ import ( "log" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "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/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "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" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -24,7 +25,7 @@ import ( // @SDKResource("aws_ssm_activation", name="Activation") // @Tags -func ResourceActivation() *schema.Resource { +func resourceActivation() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceActivationCreate, ReadWithoutTimeout: resourceActivationRead, @@ -84,7 +85,7 @@ func ResourceActivation() *schema.Resource { func resourceActivationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) name := d.Get(names.AttrName).(string) input := &ssm.CreateActivationInput{ @@ -103,12 +104,12 @@ func resourceActivationCreate(ctx context.Context, d *schema.ResourceData, meta } if v, ok := d.GetOk("registration_limit"); ok { - input.RegistrationLimit = aws.Int64(int64(v.(int))) + input.RegistrationLimit = aws.Int32(int32(v.(int))) } outputRaw, err := tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (interface{}, error) { - return conn.CreateActivationWithContext(ctx, input) - }, "ValidationException", "Nonexistent role") + return conn.CreateActivation(ctx, input) + }, errCodeValidationException, "Nonexistent role") if err != nil { return sdkdiag.AppendErrorf(diags, "creating SSM Activation (%s): %s", name, err) @@ -116,7 +117,7 @@ func resourceActivationCreate(ctx context.Context, d *schema.ResourceData, meta output := outputRaw.(*ssm.CreateActivationOutput) - d.SetId(aws.StringValue(output.ActivationId)) + d.SetId(aws.ToString(output.ActivationId)) d.Set("activation_code", output.ActivationCode) return append(diags, resourceActivationRead(ctx, d, meta)...) @@ -124,9 +125,9 @@ func resourceActivationCreate(ctx context.Context, d *schema.ResourceData, meta func resourceActivationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - activation, err := FindActivationByID(ctx, conn, d.Id()) + activation, err := findActivationByID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] SSM Activation %s not found, removing from state", d.Id()) @@ -139,7 +140,7 @@ func resourceActivationRead(ctx context.Context, d *schema.ResourceData, meta in } d.Set(names.AttrDescription, activation.Description) - d.Set("expiration_date", aws.TimeValue(activation.ExpirationDate).Format(time.RFC3339)) + d.Set("expiration_date", aws.ToTime(activation.ExpirationDate).Format(time.RFC3339)) d.Set("expired", activation.Expired) d.Set("iam_role", activation.IamRole) d.Set(names.AttrName, activation.DefaultInstanceName) @@ -153,14 +154,14 @@ func resourceActivationRead(ctx context.Context, d *schema.ResourceData, meta in func resourceActivationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) log.Printf("[DEBUG] Deleting SSM Activation: %s", d.Id()) - _, err := conn.DeleteActivationWithContext(ctx, &ssm.DeleteActivationInput{ + _, err := conn.DeleteActivation(ctx, &ssm.DeleteActivationInput{ ActivationId: aws.String(d.Id()), }) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeInvalidActivation) { + if errs.IsA[*awstypes.InvalidActivation](err) { return diags } @@ -171,12 +172,12 @@ func resourceActivationDelete(ctx context.Context, d *schema.ResourceData, meta return diags } -func FindActivationByID(ctx context.Context, conn *ssm.SSM, id string) (*ssm.Activation, error) { +func findActivationByID(ctx context.Context, conn *ssm.Client, id string) (*awstypes.Activation, error) { input := &ssm.DescribeActivationsInput{ - Filters: []*ssm.DescribeActivationsFilter{ + Filters: []awstypes.DescribeActivationsFilter{ { - FilterKey: aws.String("ActivationIds"), - FilterValues: aws.StringSlice([]string{id}), + FilterKey: awstypes.DescribeActivationsFilterKeysActivationIds, + FilterValues: []string{id}, }, }, } @@ -184,34 +185,29 @@ func FindActivationByID(ctx context.Context, conn *ssm.SSM, id string) (*ssm.Act return findActivation(ctx, conn, input) } -func findActivation(ctx context.Context, conn *ssm.SSM, input *ssm.DescribeActivationsInput) (*ssm.Activation, error) { - var output []*ssm.Activation - - err := conn.DescribeActivationsPagesWithContext(ctx, input, func(page *ssm.DescribeActivationsOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, v := range page.ActivationList { - if v != nil { - output = append(output, v) - } - } - - return !lastPage - }) +func findActivation(ctx context.Context, conn *ssm.Client, input *ssm.DescribeActivationsInput) (*awstypes.Activation, error) { + output, err := findActivations(ctx, conn, input) if err != nil { return nil, err } - if len(output) == 0 || output[0] == nil { - return nil, tfresource.NewEmptyResultError(input) - } + return tfresource.AssertSingleValueResult(output) +} + +func findActivations(ctx context.Context, conn *ssm.Client, input *ssm.DescribeActivationsInput) ([]awstypes.Activation, error) { + var output []awstypes.Activation + + pages := ssm.NewDescribeActivationsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if err != nil { + return nil, err + } - if count := len(output); count > 1 { - return nil, tfresource.NewTooManyResultsError(count, input) + output = append(output, page.ActivationList...) } - return output[0], nil + return output, nil } diff --git a/internal/service/ssm/activation_test.go b/internal/service/ssm/activation_test.go index 38f8127f9f6..154528825b9 100644 --- a/internal/service/ssm/activation_test.go +++ b/internal/service/ssm/activation_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -22,7 +22,7 @@ import ( func TestAccSSMActivation_basic(t *testing.T) { ctx := acctest.Context(t) - var ssmActivation ssm.Activation + var ssmActivation awstypes.Activation rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) roleName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_activation.test" @@ -56,7 +56,7 @@ func TestAccSSMActivation_basic(t *testing.T) { func TestAccSSMActivation_tags(t *testing.T) { ctx := acctest.Context(t) - var ssmActivation ssm.Activation + var ssmActivation awstypes.Activation rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) roleName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_activation.test" @@ -91,7 +91,7 @@ func TestAccSSMActivation_tags(t *testing.T) { func TestAccSSMActivation_expirationDate(t *testing.T) { ctx := acctest.Context(t) - var ssmActivation ssm.Activation + var ssmActivation awstypes.Activation rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) roleName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) expirationDate := time.Now().Add(48 * time.Hour).UTC().Format(time.RFC3339) @@ -124,7 +124,7 @@ func TestAccSSMActivation_expirationDate(t *testing.T) { func TestAccSSMActivation_disappears(t *testing.T) { ctx := acctest.Context(t) - var ssmActivation ssm.Activation + var ssmActivation awstypes.Activation rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) roleName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_activation.test" @@ -147,18 +147,14 @@ func TestAccSSMActivation_disappears(t *testing.T) { }) } -func testAccCheckActivationExists(ctx context.Context, n string, v *ssm.Activation) resource.TestCheckFunc { +func testAccCheckActivationExists(ctx context.Context, n string, v *awstypes.Activation) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No SSM Activation ID is set") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) output, err := tfssm.FindActivationByID(ctx, conn, rs.Primary.ID) @@ -174,7 +170,7 @@ func testAccCheckActivationExists(ctx context.Context, n string, v *ssm.Activati func testAccCheckActivationDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ssm_activation" { diff --git a/internal/service/ssm/errors.go b/internal/service/ssm/errors.go new file mode 100644 index 00000000000..006e63dca66 --- /dev/null +++ b/internal/service/ssm/errors.go @@ -0,0 +1,8 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssm + +const ( + errCodeValidationException = "ValidationException" +) diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index 0b059e774e9..bb53eba6ed9 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -5,8 +5,10 @@ package ssm // Exports for use in tests only. var ( + ResourceActivation = resourceActivation ResourceDefaultPatchBaseline = resourceDefaultPatchBaseline ResourcePatchBaseline = resourcePatchBaseline + FindActivationByID = findActivationByID FindPatchBaselineByID = findPatchBaselineByID ) diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index 348f2253007..b614290ece6 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -54,7 +54,7 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ { - Factory: ResourceActivation, + Factory: resourceActivation, TypeName: "aws_ssm_activation", Name: "Activation", Tags: &types.ServicePackageResourceTags{}, From d615ed0163c8215094c0c98e25c97fffe8ff0fb0 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sat, 11 May 2024 17:12:29 -0400 Subject: [PATCH 05/34] internal/generate/tags: Add 'TagResTypeElemType' argument. --- internal/generate/tags/main.go | 3 +++ .../generate/tags/templates/v2/list_tags_body.tmpl | 4 ++++ .../generate/tags/templates/v2/update_tags_body.tmpl | 12 ++++++++++++ internal/service/ssm/generate.go | 2 +- internal/service/ssm/tags_gen.go | 6 +++--- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/internal/generate/tags/main.go b/internal/generate/tags/main.go index 0e745b3e1fa..69b995692e1 100644 --- a/internal/generate/tags/main.go +++ b/internal/generate/tags/main.go @@ -68,6 +68,7 @@ var ( tagOp = flag.String("TagOp", "TagResource", "tagOp") tagOpBatchSize = flag.String("TagOpBatchSize", "", "tagOpBatchSize") tagResTypeElem = flag.String("TagResTypeElem", "", "tagResTypeElem") + tagResTypeElemType = flag.String("TagResTypeElemType", "", "tagResTypeElemType") tagType = flag.String("TagType", "Tag", "tagType") tagType2 = flag.String("TagType2", "", "tagType") tagTypeAddBoolElem = flag.String("TagTypeAddBoolElem", "", "TagTypeAddBoolElem") @@ -192,6 +193,7 @@ type TemplateData struct { TagOpBatchSize string TagPackage string TagResTypeElem string + TagResTypeElemType string TagType string TagType2 string TagTypeAddBoolElem string @@ -363,6 +365,7 @@ func main() { TagOpBatchSize: *tagOpBatchSize, TagPackage: tagPackage, TagResTypeElem: *tagResTypeElem, + TagResTypeElemType: *tagResTypeElemType, TagType: *tagType, TagType2: *tagType2, TagTypeAddBoolElem: *tagTypeAddBoolElem, diff --git a/internal/generate/tags/templates/v2/list_tags_body.tmpl b/internal/generate/tags/templates/v2/list_tags_body.tmpl index 59fb3c8ea7c..907bfdf8ba8 100644 --- a/internal/generate/tags/templates/v2/list_tags_body.tmpl +++ b/internal/generate/tags/templates/v2/list_tags_body.tmpl @@ -19,9 +19,13 @@ func {{ .ListTagsFunc }}(ctx context.Context, conn {{ .ClientType }}, identifier {{ .ListTagsInIDElem }}: aws.String(identifier), {{- end }} {{- if .TagResTypeElem }} + {{- if .TagResTypeElemType }} + {{ .TagResTypeElem }}: awstypes.{{ .TagResTypeElemType }}(resourceType), + {{- else }} {{ .TagResTypeElem }}: aws.String(resourceType), {{- end }} {{- end }} + {{- end }} } {{- if .ListTagsOpPaginated }} var output []awstypes.{{ or .TagType2 .TagType }} diff --git a/internal/generate/tags/templates/v2/update_tags_body.tmpl b/internal/generate/tags/templates/v2/update_tags_body.tmpl index a07714b0620..3ed05b03df7 100644 --- a/internal/generate/tags/templates/v2/update_tags_body.tmpl +++ b/internal/generate/tags/templates/v2/update_tags_body.tmpl @@ -39,9 +39,13 @@ func {{ .UpdateTagsFunc }}(ctx context.Context, conn {{ .ClientType }}, identifi {{ .TagInIDElem }}: aws.String(identifier), {{- end }} {{- if .TagResTypeElem }} + {{- if .TagResTypeElemType }} + {{ .TagResTypeElem }}: awstypes.{{ .TagResTypeElemType }}(resourceType), + {{- else }} {{ .TagResTypeElem }}: aws.String(resourceType), {{- end }} {{- end }} + {{- end }} } if len(updatedTags) > 0 { @@ -86,9 +90,13 @@ func {{ .UpdateTagsFunc }}(ctx context.Context, conn {{ .ClientType }}, identifi {{ .TagInIDElem }}: aws.String(identifier), {{- end }} {{- if .TagResTypeElem }} + {{- if .TagResTypeElemType }} + {{ .TagResTypeElem }}: awstypes.{{ .TagResTypeElemType }}(resourceType), + {{- else }} {{ .TagResTypeElem }}: aws.String(resourceType), {{- end }} {{- end }} + {{- end }} {{- if .UntagInNeedTagType }} {{ .UntagInTagsElem }}: {{ .TagsFunc }}(removedTags), {{- else if .UntagInNeedTagKeyType }} @@ -128,9 +136,13 @@ func {{ .UpdateTagsFunc }}(ctx context.Context, conn {{ .ClientType }}, identifi {{ .TagInIDElem }}: aws.String(identifier), {{- end }} {{- if .TagResTypeElem }} + {{- if .TagResTypeElemType }} + {{ .TagResTypeElem }}: awstypes.{{ .TagResTypeElemType }}(resourceType), + {{- else }} {{ .TagResTypeElem }}: aws.String(resourceType), {{- end }} {{- end }} + {{- end }} {{- if .TagInCustomVal }} {{ .TagInTagsElem }}: {{ .TagInCustomVal }}, {{- else }} diff --git a/internal/service/ssm/generate.go b/internal/service/ssm/generate.go index f40af846b28..7ab06431c6f 100644 --- a/internal/service/ssm/generate.go +++ b/internal/service/ssm/generate.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -ListTags -ListTagsInIDElem=ResourceId -ListTagsOutTagsElem=TagList -ServiceTagsSlice -TagOp=AddTagsToResource -TagInIDElem=ResourceId -TagResTypeElem=ResourceType -UntagOp=RemoveTagsFromResource -UpdateTags -CreateTags +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -ListTags -ListTagsInIDElem=ResourceId -ListTagsOutTagsElem=TagList -ServiceTagsSlice -TagOp=AddTagsToResource -TagInIDElem=ResourceId -TagResTypeElem=ResourceType -TagResTypeElemType=ResourceTypeForTagging -UntagOp=RemoveTagsFromResource -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/ssm/tags_gen.go b/internal/service/ssm/tags_gen.go index 39f2235eab5..5a17cb75c31 100644 --- a/internal/service/ssm/tags_gen.go +++ b/internal/service/ssm/tags_gen.go @@ -22,7 +22,7 @@ import ( func listTags(ctx context.Context, conn *ssm.Client, identifier, resourceType string, optFns ...func(*ssm.Options)) (tftags.KeyValueTags, error) { input := &ssm.ListTagsForResourceInput{ ResourceId: aws.String(identifier), - ResourceType: aws.String(resourceType), + ResourceType: awstypes.ResourceTypeForTagging(resourceType), } output, err := conn.ListTagsForResource(ctx, input, optFns...) @@ -121,7 +121,7 @@ func updateTags(ctx context.Context, conn *ssm.Client, identifier, resourceType if len(removedTags) > 0 { input := &ssm.RemoveTagsFromResourceInput{ ResourceId: aws.String(identifier), - ResourceType: aws.String(resourceType), + ResourceType: awstypes.ResourceTypeForTagging(resourceType), TagKeys: removedTags.Keys(), } @@ -137,7 +137,7 @@ func updateTags(ctx context.Context, conn *ssm.Client, identifier, resourceType if len(updatedTags) > 0 { input := &ssm.AddTagsToResourceInput{ ResourceId: aws.String(identifier), - ResourceType: aws.String(resourceType), + ResourceType: awstypes.ResourceTypeForTagging(resourceType), Tags: Tags(updatedTags), } From 3ebd0b99b847ca42edbb3b59db47165f896f15cf Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sat, 11 May 2024 17:56:37 -0400 Subject: [PATCH 06/34] r/aws_ssm_association: Migrate to AWS SDK for Go v2. --- internal/service/ssm/association.go | 375 ++++++++++-------- internal/service/ssm/association_migrate.go | 3 +- .../service/ssm/association_migrate_test.go | 6 +- internal/service/ssm/association_test.go | 19 +- internal/service/ssm/exports_test.go | 2 + internal/service/ssm/find.go | 24 -- internal/service/ssm/flex.go | 57 +-- internal/service/ssm/service_package_gen.go | 3 +- internal/service/ssm/status.go | 22 - internal/service/ssm/wait.go | 23 -- 10 files changed, 251 insertions(+), 283 deletions(-) diff --git a/internal/service/ssm/association.go b/internal/service/ssm/association.go index 3d91c1221d0..0a23f755aaf 100644 --- a/internal/service/ssm/association.go +++ b/internal/service/ssm/association.go @@ -5,49 +5,59 @@ package ssm import ( "context" - "fmt" + "errors" "log" + "strings" "time" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/service/ssm" - "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/aws/arn" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/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/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + tfmaps "github.com/hashicorp/terraform-provider-aws/internal/maps" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_ssm_association") -func ResourceAssociation() *schema.Resource { +// @SDKResource("aws_ssm_association", name="Association") +func resourceAssociation() *schema.Resource { //lintignore:R011 return &schema.Resource{ CreateWithoutTimeout: resourceAssociationCreate, ReadWithoutTimeout: resourceAssociationRead, UpdateWithoutTimeout: resourceAssociationUpdate, DeleteWithoutTimeout: resourceAssociationDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, - MigrateState: AssociationMigrateState, + MigrateState: associationMigrateState, SchemaVersion: 1, Schema: map[string]*schema.Schema{ - names.AttrARN: { - Type: schema.TypeString, - Computed: true, - }, "apply_only_at_cron_interval": { Type: schema.TypeBool, Default: false, Optional: true, }, + names.AttrARN: { + Type: schema.TypeString, + Computed: true, + }, + "association_id": { + Type: schema.TypeString, + Computed: true, + }, "association_name": { Type: schema.TypeString, Optional: true, @@ -56,19 +66,15 @@ func ResourceAssociation() *schema.Resource { validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z_.-]{3,128}$`), "must contain only alphanumeric, underscore, hyphen, or period characters"), ), }, - "association_id": { - Type: schema.TypeString, - Computed: true, - }, "automation_target_parameter_name": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringLenBetween(1, 50), }, "compliance_severity": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(ssm.ComplianceSeverity_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.ComplianceSeverity](), }, "document_version": { Type: schema.TypeString, @@ -76,6 +82,12 @@ func ResourceAssociation() *schema.Resource { Computed: true, ValidateFunc: validation.StringMatch(regexache.MustCompile(`^([$]LATEST|[$]DEFAULT|^[1-9][0-9]*$)$`), ""), }, + names.AttrInstanceID: { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Deprecated: "use 'targets' argument instead. https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_CreateAssociation.html#systemsmanager-CreateAssociation-request-InstanceId", + }, "max_concurrency": { Type: schema.TypeString, Optional: true, @@ -91,12 +103,6 @@ func ResourceAssociation() *schema.Resource { ForceNew: true, Required: true, }, - names.AttrInstanceID: { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - Deprecated: "use 'targets' argument instead. https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_CreateAssociation.html#systemsmanager-CreateAssociation-request-InstanceId", - }, "output_location": { Type: schema.TypeList, MaxItems: 1, @@ -133,9 +139,9 @@ func ResourceAssociation() *schema.Resource { ValidateFunc: validation.StringLenBetween(1, 256), }, "sync_compliance": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(ssm.AssociationSyncCompliance_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.AssociationSyncCompliance](), }, "targets": { Type: schema.TypeList, @@ -168,82 +174,76 @@ func ResourceAssociation() *schema.Resource { func resourceAssociationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) - - log.Printf("[DEBUG] SSM association create: %s", d.Id()) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - associationInput := &ssm.CreateAssociationInput{ - Name: aws.String(d.Get(names.AttrName).(string)), + name := d.Get(names.AttrName).(string) + input := &ssm.CreateAssociationInput{ + Name: aws.String(name), } if v, ok := d.GetOk("apply_only_at_cron_interval"); ok { - associationInput.ApplyOnlyAtCronInterval = aws.Bool(v.(bool)) + input.ApplyOnlyAtCronInterval = v.(bool) } if v, ok := d.GetOk("association_name"); ok { - associationInput.AssociationName = aws.String(v.(string)) + input.AssociationName = aws.String(v.(string)) } - if v, ok := d.GetOk(names.AttrInstanceID); ok { - associationInput.InstanceId = aws.String(v.(string)) + if v, ok := d.GetOk("automation_target_parameter_name"); ok { + input.AutomationTargetParameterName = aws.String(v.(string)) } - if v, ok := d.GetOk("document_version"); ok { - associationInput.DocumentVersion = aws.String(v.(string)) + if v, ok := d.GetOk("compliance_severity"); ok { + input.ComplianceSeverity = awstypes.AssociationComplianceSeverity(v.(string)) } - if v, ok := d.GetOk(names.AttrParameters); ok { - associationInput.Parameters = expandDocumentParameters(v.(map[string]interface{})) + if v, ok := d.GetOk("document_version"); ok { + input.DocumentVersion = aws.String(v.(string)) } - if v, ok := d.GetOk(names.AttrScheduleExpression); ok { - associationInput.ScheduleExpression = aws.String(v.(string)) + if v, ok := d.GetOk(names.AttrInstanceID); ok { + input.InstanceId = aws.String(v.(string)) } - if v, ok := d.GetOk("sync_compliance"); ok { - associationInput.SyncCompliance = aws.String(v.(string)) + if v, ok := d.GetOk("max_concurrency"); ok { + input.MaxConcurrency = aws.String(v.(string)) } - if v, ok := d.GetOk("targets"); ok { - associationInput.Targets = expandTargets(v.([]interface{})) + if v, ok := d.GetOk("max_errors"); ok { + input.MaxErrors = aws.String(v.(string)) } if v, ok := d.GetOk("output_location"); ok { - associationInput.OutputLocation = expandAssociationOutputLocation(v.([]interface{})) + input.OutputLocation = expandAssociationOutputLocation(v.([]interface{})) } - if v, ok := d.GetOk("compliance_severity"); ok { - associationInput.ComplianceSeverity = aws.String(v.(string)) + if v, ok := d.GetOk(names.AttrParameters); ok { + input.Parameters = expandParameters(v.(map[string]interface{})) } - if v, ok := d.GetOk("max_concurrency"); ok { - associationInput.MaxConcurrency = aws.String(v.(string)) + if v, ok := d.GetOk(names.AttrScheduleExpression); ok { + input.ScheduleExpression = aws.String(v.(string)) } - if v, ok := d.GetOk("max_errors"); ok { - associationInput.MaxErrors = aws.String(v.(string)) + if v, ok := d.GetOk("sync_compliance"); ok { + input.SyncCompliance = awstypes.AssociationSyncCompliance(v.(string)) } - if v, ok := d.GetOk("automation_target_parameter_name"); ok { - associationInput.AutomationTargetParameterName = aws.String(v.(string)) + if v, ok := d.GetOk("targets"); ok { + input.Targets = expandTargets(v.([]interface{})) } - resp, err := conn.CreateAssociationWithContext(ctx, associationInput) - if err != nil { - return sdkdiag.AppendErrorf(diags, "creating SSM association: %s", err) - } + output, err := conn.CreateAssociation(ctx, input) - if resp.AssociationDescription == nil { - return sdkdiag.AppendErrorf(diags, "AssociationDescription was nil") + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating SSM Association (%s): %s", name, err) } - d.SetId(aws.StringValue(resp.AssociationDescription.AssociationId)) + d.SetId(aws.ToString(output.AssociationDescription.AssociationId)) if v, ok := d.GetOk("wait_for_success_timeout_seconds"); ok { - dur, _ := time.ParseDuration(fmt.Sprintf("%ds", v.(int))) - _, err = waitAssociationSuccess(ctx, conn, d.Id(), dur) - if err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for SSM Association (%s) to be Success: %s", d.Id(), err) + if _, err := waitAssociationCreated(ctx, conn, d.Id(), time.Duration(v.(int))*time.Second); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for SSM Association (%s) create: %s", d.Id(), err) } } @@ -252,51 +252,48 @@ func resourceAssociationCreate(ctx context.Context, d *schema.ResourceData, meta func resourceAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - log.Printf("[DEBUG] Reading SSM Association: %s", d.Id()) + association, err := findAssociationByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSM Association %s not found, removing from state", d.Id()) + d.SetId("") + return diags + } - association, err := FindAssociationById(ctx, conn, d.Id()) if err != nil { - if !d.IsNewResource() && tfresource.NotFound(err) { - d.SetId("") - log.Printf("[WARN] Unable to find SSM Association (%s); removing from state", d.Id()) - return diags - } return sdkdiag.AppendErrorf(diags, "reading SSM Association (%s): %s", d.Id(), err) } + d.Set("apply_only_at_cron_interval", association.ApplyOnlyAtCronInterval) arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, Service: "ssm", Region: meta.(*conns.AWSClient).Region, AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("association/%s", aws.StringValue(association.AssociationId)), + Resource: "association/" + aws.ToString(association.AssociationId), }.String() d.Set(names.AttrARN, arn) - d.Set("apply_only_at_cron_interval", association.ApplyOnlyAtCronInterval) - d.Set("association_name", association.AssociationName) - d.Set(names.AttrInstanceID, association.InstanceId) - d.Set(names.AttrName, association.Name) d.Set("association_id", association.AssociationId) - d.Set(names.AttrScheduleExpression, association.ScheduleExpression) - d.Set("sync_compliance", association.SyncCompliance) - d.Set("document_version", association.DocumentVersion) + d.Set("association_name", association.AssociationName) + d.Set("automation_target_parameter_name", association.AutomationTargetParameterName) d.Set("compliance_severity", association.ComplianceSeverity) + d.Set("document_version", association.DocumentVersion) + d.Set(names.AttrInstanceID, association.InstanceId) d.Set("max_concurrency", association.MaxConcurrency) d.Set("max_errors", association.MaxErrors) - d.Set("automation_target_parameter_name", association.AutomationTargetParameterName) - + d.Set(names.AttrName, association.Name) + if err := d.Set("output_location", flattenAssociationOutputLocation(association.OutputLocation)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting output_location: %s", err) + } if err := d.Set(names.AttrParameters, flattenParameters(association.Parameters)); err != nil { - return sdkdiag.AppendErrorf(diags, "reading SSM Association (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "setting parameters: %s", err) } - + d.Set(names.AttrScheduleExpression, association.ScheduleExpression) + d.Set("sync_compliance", association.SyncCompliance) if err := d.Set("targets", flattenTargets(association.Targets)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting targets error: %s", err) - } - - if err := d.Set("output_location", flattenAssociationOutputLocation(association.OutputLocation)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting output_location error: %s", err) + return sdkdiag.AppendErrorf(diags, "setting targets: %s", err) } return diags @@ -304,66 +301,65 @@ func resourceAssociationRead(ctx context.Context, d *schema.ResourceData, meta i func resourceAssociationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - log.Printf("[DEBUG] SSM Association update: %s", d.Id()) - - associationInput := &ssm.UpdateAssociationInput{ + // AWS creates a new version every time the association is updated, so everything should be passed in the update. + input := &ssm.UpdateAssociationInput{ AssociationId: aws.String(d.Id()), } if v, ok := d.GetOk("apply_only_at_cron_interval"); ok { - associationInput.ApplyOnlyAtCronInterval = aws.Bool(v.(bool)) + input.ApplyOnlyAtCronInterval = v.(bool) } - // AWS creates a new version every time the association is updated, so everything should be passed in the update. if v, ok := d.GetOk("association_name"); ok { - associationInput.AssociationName = aws.String(v.(string)) + input.AssociationName = aws.String(v.(string)) } - if v, ok := d.GetOk("document_version"); ok { - associationInput.DocumentVersion = aws.String(v.(string)) + if v, ok := d.GetOk("automation_target_parameter_name"); ok { + input.AutomationTargetParameterName = aws.String(v.(string)) } - if v, ok := d.GetOk(names.AttrScheduleExpression); ok { - associationInput.ScheduleExpression = aws.String(v.(string)) + if v, ok := d.GetOk("compliance_severity"); ok { + input.ComplianceSeverity = awstypes.AssociationComplianceSeverity(v.(string)) } - if d.HasChange("sync_compliance") { - associationInput.SyncCompliance = aws.String(d.Get("sync_compliance").(string)) + if v, ok := d.GetOk("document_version"); ok { + input.DocumentVersion = aws.String(v.(string)) } - if v, ok := d.GetOk(names.AttrParameters); ok { - associationInput.Parameters = expandDocumentParameters(v.(map[string]interface{})) + if v, ok := d.GetOk("max_concurrency"); ok { + input.MaxConcurrency = aws.String(v.(string)) } - if _, ok := d.GetOk("targets"); ok { - associationInput.Targets = expandTargets(d.Get("targets").([]interface{})) + if v, ok := d.GetOk("max_errors"); ok { + input.MaxErrors = aws.String(v.(string)) } if v, ok := d.GetOk("output_location"); ok { - associationInput.OutputLocation = expandAssociationOutputLocation(v.([]interface{})) + input.OutputLocation = expandAssociationOutputLocation(v.([]interface{})) } - if v, ok := d.GetOk("compliance_severity"); ok { - associationInput.ComplianceSeverity = aws.String(v.(string)) + if v, ok := d.GetOk(names.AttrParameters); ok { + input.Parameters = expandParameters(v.(map[string]interface{})) } - if v, ok := d.GetOk("max_concurrency"); ok { - associationInput.MaxConcurrency = aws.String(v.(string)) + if v, ok := d.GetOk(names.AttrScheduleExpression); ok { + input.ScheduleExpression = aws.String(v.(string)) } - if v, ok := d.GetOk("max_errors"); ok { - associationInput.MaxErrors = aws.String(v.(string)) + if d.HasChange("sync_compliance") { + input.SyncCompliance = awstypes.AssociationSyncCompliance(d.Get("sync_compliance").(string)) } - if v, ok := d.GetOk("automation_target_parameter_name"); ok { - associationInput.AutomationTargetParameterName = aws.String(v.(string)) + if _, ok := d.GetOk("targets"); ok { + input.Targets = expandTargets(d.Get("targets").([]interface{})) } - _, err := conn.UpdateAssociationWithContext(ctx, associationInput) + _, err := conn.UpdateAssociation(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "updating SSM association: %s", err) + return sdkdiag.AppendErrorf(diags, "updating SSM Association (%s): %s", d.Id(), err) } return append(diags, resourceAssociationRead(ctx, d, meta)...) @@ -371,81 +367,144 @@ func resourceAssociationUpdate(ctx context.Context, d *schema.ResourceData, meta func resourceAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) log.Printf("[DEBUG] Deleting SSM Association: %s", d.Id()) - - params := &ssm.DeleteAssociationInput{ + _, err := conn.DeleteAssociation(ctx, &ssm.DeleteAssociationInput{ AssociationId: aws.String(d.Id()), - } + }) - _, err := conn.DeleteAssociationWithContext(ctx, params) + if errs.IsA[*awstypes.AssociationDoesNotExist](err) { + return diags + } if err != nil { - if tfawserr.ErrCodeContains(err, ssm.ErrCodeAssociationDoesNotExist) { - return diags - } - return sdkdiag.AppendErrorf(diags, "deleting SSM association: %s", err) + return sdkdiag.AppendErrorf(diags, "deleting SSM Association (%s): %s", d.Id(), err) } return diags } -func expandDocumentParameters(params map[string]interface{}) map[string][]*string { - var docParams = make(map[string][]*string) - for k, v := range params { - values := make([]*string, 1) - values[0] = aws.String(v.(string)) - docParams[k] = values +func findAssociationByID(ctx context.Context, conn *ssm.Client, id string) (*awstypes.AssociationDescription, error) { + input := &ssm.DescribeAssociationInput{ + AssociationId: aws.String(id), + } + + output, err := conn.DescribeAssociation(ctx, input) + + if errs.IsA[*awstypes.AssociationDoesNotExist](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err } - return docParams + if output == nil || output.AssociationDescription == nil || output.AssociationDescription.Overview == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.AssociationDescription, nil +} + +func statusAssociation(ctx context.Context, conn *ssm.Client, id string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findAssociationByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + // Use the Overview.Status field instead of the root-level Status as DescribeAssociation + // does not appear to return the root-level Status in the API response at this time. + return output, aws.ToString(output.Overview.Status), nil + } +} + +func waitAssociationCreated(ctx context.Context, conn *ssm.Client, id string, timeout time.Duration) (*awstypes.AssociationDescription, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.AssociationStatusNamePending), + Target: enum.Slice(awstypes.AssociationStatusNameSuccess), + Refresh: statusAssociation(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*awstypes.AssociationDescription); ok { + if status := awstypes.AssociationStatusName(aws.ToString(output.Overview.Status)); status == awstypes.AssociationStatusNameFailed { + tfresource.SetLastError(err, errors.New(aws.ToString(output.Overview.DetailedStatus))) + } + + return output, err + } + + return nil, err +} + +func expandParameters(tfMap map[string]interface{}) map[string][]string { + return tfmaps.ApplyToAllValues(tfMap, func(v interface{}) []string { + return []string{v.(string)} + }) +} + +func flattenParameters(apiObject map[string][]string) map[string]interface{} { + return tfmaps.ApplyToAllValues(apiObject, func(v []string) interface{} { + return strings.Join(v, ",") + }) } -func expandAssociationOutputLocation(config []interface{}) *ssm.InstanceAssociationOutputLocation { - if config == nil { +func expandAssociationOutputLocation(tfList []interface{}) *awstypes.InstanceAssociationOutputLocation { + if tfList == nil { return nil } //We only allow 1 Item so we can grab the first in the list only - locationConfig := config[0].(map[string]interface{}) + tfMap := tfList[0].(map[string]interface{}) - S3OutputLocation := &ssm.S3OutputLocation{ - OutputS3BucketName: aws.String(locationConfig[names.AttrS3BucketName].(string)), + s3OutputLocation := &awstypes.S3OutputLocation{ + OutputS3BucketName: aws.String(tfMap[names.AttrS3BucketName].(string)), } - if v, ok := locationConfig["s3_key_prefix"]; ok { - S3OutputLocation.OutputS3KeyPrefix = aws.String(v.(string)) + if v, ok := tfMap["s3_key_prefix"]; ok { + s3OutputLocation.OutputS3KeyPrefix = aws.String(v.(string)) } - if v, ok := locationConfig["s3_region"].(string); ok && v != "" { - S3OutputLocation.OutputS3Region = aws.String(v) + if v, ok := tfMap["s3_region"].(string); ok && v != "" { + s3OutputLocation.OutputS3Region = aws.String(v) } - return &ssm.InstanceAssociationOutputLocation{ - S3Location: S3OutputLocation, + return &awstypes.InstanceAssociationOutputLocation{ + S3Location: s3OutputLocation, } } -func flattenAssociationOutputLocation(location *ssm.InstanceAssociationOutputLocation) []map[string]interface{} { - if location == nil || location.S3Location == nil { +func flattenAssociationOutputLocation(apiObject *awstypes.InstanceAssociationOutputLocation) []interface{} { + if apiObject == nil || apiObject.S3Location == nil { return nil } - result := make([]map[string]interface{}, 0) - item := make(map[string]interface{}) + tfList := make([]interface{}, 0) + tfMap := make(map[string]interface{}) - item[names.AttrS3BucketName] = aws.StringValue(location.S3Location.OutputS3BucketName) + tfMap[names.AttrS3BucketName] = aws.ToString(apiObject.S3Location.OutputS3BucketName) - if location.S3Location.OutputS3KeyPrefix != nil { - item["s3_key_prefix"] = aws.StringValue(location.S3Location.OutputS3KeyPrefix) + if apiObject.S3Location.OutputS3KeyPrefix != nil { + tfMap["s3_key_prefix"] = aws.ToString(apiObject.S3Location.OutputS3KeyPrefix) } - if location.S3Location.OutputS3Region != nil { - item["s3_region"] = aws.StringValue(location.S3Location.OutputS3Region) + if apiObject.S3Location.OutputS3Region != nil { + tfMap["s3_region"] = aws.ToString(apiObject.S3Location.OutputS3Region) } - result = append(result, item) + tfList = append(tfList, tfMap) - return result + return tfList } diff --git a/internal/service/ssm/association_migrate.go b/internal/service/ssm/association_migrate.go index 939863b9350..ed8c01bbc08 100644 --- a/internal/service/ssm/association_migrate.go +++ b/internal/service/ssm/association_migrate.go @@ -11,8 +11,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func AssociationMigrateState( - v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { +func associationMigrateState(v int, is *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { switch v { case 0: log.Println("[INFO] Found AWS SSM Association State v0; migrating to v1") diff --git a/internal/service/ssm/association_migrate_test.go b/internal/service/ssm/association_migrate_test.go index cf3f3057d72..8b184516428 100644 --- a/internal/service/ssm/association_migrate_test.go +++ b/internal/service/ssm/association_migrate_test.go @@ -1,13 +1,12 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package ssm_test +package ssm import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - tfssm "github.com/hashicorp/terraform-provider-aws/internal/service/ssm" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -38,8 +37,7 @@ func TestAssociationRuleMigrateState(t *testing.T) { ID: tc.ID, Attributes: tc.Attributes, } - is, err := tfssm.AssociationMigrateState( - tc.StateVersion, is, tc.Meta) + is, err := associationMigrateState(tc.StateVersion, is, tc.Meta) if err != nil { t.Fatalf("bad: %s, err: %#v", tn, err) diff --git a/internal/service/ssm/association_test.go b/internal/service/ssm/association_test.go index 6675acfe6b8..db02043c18a 100644 --- a/internal/service/ssm/association_test.go +++ b/internal/service/ssm/association_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -647,13 +646,9 @@ func testAccCheckAssociationExists(ctx context.Context, n string) resource.TestC return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No SSM Assosciation ID is set") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) - _, err := tfssm.FindAssociationById(ctx, conn, rs.Primary.ID) + _, err := tfssm.FindAssociationByID(ctx, conn, rs.Primary.ID) return err } @@ -661,26 +656,24 @@ func testAccCheckAssociationExists(ctx context.Context, n string) resource.TestC func testAccCheckAssociationDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ssm_association" { continue } - assoc, err := tfssm.FindAssociationById(ctx, conn, rs.Primary.ID) + _, err := tfssm.FindAssociationByID(ctx, conn, rs.Primary.ID) if tfresource.NotFound(err) { continue } if err != nil { - return fmt.Errorf("error reading SSM Association (%s): %w", rs.Primary.ID, err) + return err } - if aws.StringValue(assoc.AssociationId) == rs.Primary.ID { - return fmt.Errorf("SSM Association %q still exists", rs.Primary.ID) - } + return fmt.Errorf("SSM Association %s still exists", rs.Primary.ID) } return nil diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index bb53eba6ed9..f9e8c53792c 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -6,9 +6,11 @@ package ssm // Exports for use in tests only. var ( ResourceActivation = resourceActivation + ResourceAssociation = resourceAssociation ResourceDefaultPatchBaseline = resourceDefaultPatchBaseline ResourcePatchBaseline = resourcePatchBaseline FindActivationByID = findActivationByID + FindAssociationByID = findAssociationByID FindPatchBaselineByID = findPatchBaselineByID ) diff --git a/internal/service/ssm/find.go b/internal/service/ssm/find.go index 19e5e95f817..b804dbb3968 100644 --- a/internal/service/ssm/find.go +++ b/internal/service/ssm/find.go @@ -13,30 +13,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -func FindAssociationById(ctx context.Context, conn *ssm.SSM, id string) (*ssm.AssociationDescription, error) { - input := &ssm.DescribeAssociationInput{ - AssociationId: aws.String(id), - } - - output, err := conn.DescribeAssociationWithContext(ctx, input) - if tfawserr.ErrCodeContains(err, ssm.ErrCodeAssociationDoesNotExist) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil || output.AssociationDescription == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output.AssociationDescription, nil -} - // FindPatchGroup returns matching SSM Patch Group by Patch Group and BaselineId. func FindPatchGroup(ctx context.Context, conn *ssm.SSM, patchGroup, baselineId string) (*ssm.PatchGroupPatchBaselineMapping, error) { input := &ssm.DescribePatchGroupsInput{} diff --git a/internal/service/ssm/flex.go b/internal/service/ssm/flex.go index 0c04f6dfc25..d5eb7242411 100644 --- a/internal/service/ssm/flex.go +++ b/internal/service/ssm/flex.go @@ -4,58 +4,43 @@ package ssm import ( - "strings" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go-v2/aws" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/names" ) -func expandTargets(in []interface{}) []*ssm.Target { - targets := make([]*ssm.Target, 0) +func expandTargets(tfList []interface{}) []awstypes.Target { + apiObjects := make([]awstypes.Target, 0) - for _, tConfig := range in { - config := tConfig.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) - target := &ssm.Target{ - Key: aws.String(config[names.AttrKey].(string)), - Values: flex.ExpandStringList(config[names.AttrValues].([]interface{})), + apiObject := awstypes.Target{ + Key: aws.String(tfMap[names.AttrKey].(string)), + Values: flex.ExpandStringValueList(tfMap[names.AttrValues].([]interface{})), } - targets = append(targets, target) + apiObjects = append(apiObjects, apiObject) } - return targets -} - -func flattenParameters(parameters map[string][]*string) map[string]string { - result := make(map[string]string) - for p, values := range parameters { - var vs []string - for _, vPtr := range values { - if v := aws.StringValue(vPtr); v != "" { - vs = append(vs, v) - } - } - result[p] = strings.Join(vs, ",") - } - return result + return apiObjects } -func flattenTargets(targets []*ssm.Target) []map[string]interface{} { - if len(targets) == 0 { +func flattenTargets(apiObjects []awstypes.Target) []interface{} { + if len(apiObjects) == 0 { return nil } - result := make([]map[string]interface{}, 0, len(targets)) - for _, target := range targets { - t := make(map[string]interface{}, 1) - t[names.AttrKey] = aws.StringValue(target.Key) - t[names.AttrValues] = flex.FlattenStringList(target.Values) + tfList := make([]interface{}, 0, len(apiObjects)) + + for _, apiObject := range apiObjects { + tfMap := make(map[string]interface{}, 1) + tfMap[names.AttrKey] = aws.ToString(apiObject.Key) + tfMap[names.AttrValues] = apiObject.Values - result = append(result, t) + tfList = append(tfList, tfMap) } - return result + return tfList } diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index b614290ece6..3782f2819f8 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -60,8 +60,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Tags: &types.ServicePackageResourceTags{}, }, { - Factory: ResourceAssociation, + Factory: resourceAssociation, TypeName: "aws_ssm_association", + Name: "Association", }, { Factory: resourceDefaultPatchBaseline, diff --git a/internal/service/ssm/status.go b/internal/service/ssm/status.go index 0452199c0f2..6eaaee50550 100644 --- a/internal/service/ssm/status.go +++ b/internal/service/ssm/status.go @@ -12,28 +12,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -func statusAssociation(ctx context.Context, conn *ssm.SSM, id string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - output, err := FindAssociationById(ctx, conn, id) - - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - // Use the Overview.Status field instead of the root-level Status as DescribeAssociation - // does not appear to return the root-level Status in the API response at this time. - if output.Overview == nil { - return nil, "", nil - } - - return output, aws.StringValue(output.Overview.Status), nil - } -} - func statusServiceSetting(ctx context.Context, conn *ssm.SSM, id string) retry.StateRefreshFunc { return func() (interface{}, string, error) { output, err := FindServiceSettingByID(ctx, conn, id) diff --git a/internal/service/ssm/wait.go b/internal/service/ssm/wait.go index e817bde1bf5..f5a5d802ac2 100644 --- a/internal/service/ssm/wait.go +++ b/internal/service/ssm/wait.go @@ -5,35 +5,12 @@ package ssm import ( "context" - "errors" "time" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ssm" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -func waitAssociationSuccess(ctx context.Context, conn *ssm.SSM, id string, timeout time.Duration) (*ssm.AssociationDescription, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{ssm.AssociationStatusNamePending}, - Target: []string{ssm.AssociationStatusNameSuccess}, - Refresh: statusAssociation(ctx, conn, id), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if output, ok := outputRaw.(*ssm.AssociationDescription); ok && output.Overview != nil { - if status := aws.StringValue(output.Overview.Status); status == ssm.AssociationStatusNameFailed { - tfresource.SetLastError(err, errors.New(aws.StringValue(output.Overview.DetailedStatus))) - } - return output, err - } - - return nil, err -} - func waitServiceSettingUpdated(ctx context.Context, conn *ssm.SSM, id string, timeout time.Duration) (*ssm.ServiceSetting, error) { stateConf := &retry.StateChangeConf{ Pending: []string{"PendingUpdate", ""}, From 7b3764d18d2eddca487bbcce0ae1866164ad11d6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sun, 12 May 2024 17:33:13 -0400 Subject: [PATCH 07/34] r/aws_ssm_document: Migrate to AWS SDK for Go v2. --- internal/service/ssm/document.go | 181 ++++++++++---------- internal/service/ssm/document_test.go | 8 +- internal/service/ssm/exports_test.go | 2 + internal/service/ssm/service_package_gen.go | 2 +- 4 files changed, 94 insertions(+), 99 deletions(-) diff --git a/internal/service/ssm/document.go b/internal/service/ssm/document.go index c55e31ced19..dc81083802b 100644 --- a/internal/service/ssm/document.go +++ b/internal/service/ssm/document.go @@ -13,16 +13,18 @@ import ( "time" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/service/ssm" - "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/aws/arn" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "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/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" "github.com/hashicorp/terraform-provider-aws/internal/slices" @@ -39,12 +41,13 @@ const ( // @SDKResource("aws_ssm_document", name="Document") // @Tags(identifierAttribute="id", resourceType="Document") -func ResourceDocument() *schema.Resource { +func resourceDocument() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceDocumentCreate, ReadWithoutTimeout: resourceDocumentRead, UpdateWithoutTimeout: resourceDocumentUpdate, DeleteWithoutTimeout: resourceDocumentDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -61,9 +64,9 @@ func ResourceDocument() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ names.AttrKey: { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(ssm.AttachmentsSourceKey_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.AttachmentsSourceKey](), }, names.AttrName: { Type: schema.TypeString, @@ -102,15 +105,15 @@ func ResourceDocument() *schema.Resource { Computed: true, }, "document_format": { - Type: schema.TypeString, - Optional: true, - Default: ssm.DocumentFormatJson, - ValidateFunc: validation.StringInSlice(ssm.DocumentFormat_Values(), false), + Type: schema.TypeString, + Optional: true, + Default: awstypes.DocumentFormatJson, + ValidateDiagFunc: enum.Validate[awstypes.DocumentFormat](), }, "document_type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(ssm.DocumentType_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.DocumentType](), }, "document_version": { Type: schema.TypeString, @@ -214,8 +217,8 @@ func ResourceDocument() *schema.Resource { tfMap := flex.ExpandStringValueMap(v.(map[string]interface{})) if v, ok := tfMap[names.AttrType]; ok { - if v != ssm.DocumentPermissionTypeShare { - return fmt.Errorf("%q: only %s \"type\" supported", "permissions", ssm.DocumentPermissionTypeShare) + if awstypes.DocumentPermissionType(v) != awstypes.DocumentPermissionTypeShare { + return fmt.Errorf("%q: only %s \"type\" supported", "permissions", awstypes.DocumentPermissionTypeShare) } } else { return fmt.Errorf("%q: \"type\" must be defined", "permissions") @@ -252,13 +255,13 @@ func ResourceDocument() *schema.Resource { func resourceDocumentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) name := d.Get(names.AttrName).(string) input := &ssm.CreateDocumentInput{ Content: aws.String(d.Get("content").(string)), - DocumentFormat: aws.String(d.Get("document_format").(string)), - DocumentType: aws.String(d.Get("document_type").(string)), + DocumentFormat: awstypes.DocumentFormat(d.Get("document_format").(string)), + DocumentType: awstypes.DocumentType(d.Get("document_type").(string)), Name: aws.String(name), Tags: getTagsIn(ctx), } @@ -275,13 +278,13 @@ func resourceDocumentCreate(ctx context.Context, d *schema.ResourceData, meta in input.VersionName = aws.String(v.(string)) } - output, err := conn.CreateDocumentWithContext(ctx, input) + output, err := conn.CreateDocument(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "creating SSM Document (%s): %s", name, err) } - d.SetId(aws.StringValue(output.DocumentDescription.Name)) + d.SetId(aws.ToString(output.DocumentDescription.Name)) if v, ok := d.GetOk("permissions"); ok && len(v.(map[string]interface{})) > 0 { tfMap := flex.ExpandStringValueMap(v.(map[string]interface{})) @@ -291,12 +294,12 @@ func resourceDocumentCreate(ctx context.Context, d *schema.ResourceData, meta in for _, chunk := range chunks { input := &ssm.ModifyDocumentPermissionInput{ - AccountIdsToAdd: aws.StringSlice(chunk), + AccountIdsToAdd: chunk, Name: aws.String(d.Id()), - PermissionType: aws.String(ssm.DocumentPermissionTypeShare), + PermissionType: awstypes.DocumentPermissionTypeShare, } - _, err := conn.ModifyDocumentPermissionWithContext(ctx, input) + _, err := conn.ModifyDocumentPermission(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "modifying SSM Document (%s) permissions: %s", d.Id(), err) @@ -314,9 +317,9 @@ func resourceDocumentCreate(ctx context.Context, d *schema.ResourceData, meta in func resourceDocumentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - doc, err := FindDocumentByName(ctx, conn, d.Id()) + doc, err := findDocumentByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] SSM Document %s not found, removing from state", d.Id()) @@ -333,10 +336,10 @@ func resourceDocumentRead(ctx context.Context, d *schema.ResourceData, meta inte Service: "ssm", Region: meta.(*conns.AWSClient).Region, AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("document/%s", aws.StringValue(doc.Name)), + Resource: "document/" + aws.ToString(doc.Name), }.String() d.Set(names.AttrARN, arn) - d.Set(names.AttrCreatedDate, aws.TimeValue(doc.CreatedDate).Format(time.RFC3339)) + d.Set(names.AttrCreatedDate, aws.ToTime(doc.CreatedDate).Format(time.RFC3339)) d.Set("default_version", doc.DefaultVersion) d.Set(names.AttrDescription, doc.Description) d.Set("document_format", doc.DocumentFormat) @@ -350,7 +353,7 @@ func resourceDocumentRead(ctx context.Context, d *schema.ResourceData, meta inte if err := d.Set(names.AttrParameter, flattenDocumentParameters(doc.Parameters)); err != nil { return sdkdiag.AppendErrorf(diags, "setting parameter: %s", err) } - d.Set("platform_types", aws.StringValueSlice(doc.PlatformTypes)) + d.Set("platform_types", doc.PlatformTypes) d.Set("schema_version", doc.SchemaVersion) d.Set(names.AttrStatus, doc.Status) d.Set("target_type", doc.TargetType) @@ -358,12 +361,12 @@ func resourceDocumentRead(ctx context.Context, d *schema.ResourceData, meta inte { input := &ssm.GetDocumentInput{ - DocumentFormat: aws.String(d.Get("document_format").(string)), + DocumentFormat: awstypes.DocumentFormat(d.Get("document_format").(string)), DocumentVersion: aws.String("$LATEST"), Name: aws.String(d.Id()), } - output, err := conn.GetDocumentWithContext(ctx, input) + output, err := conn.GetDocument(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "reading SSM Document (%s) content: %s", d.Id(), err) @@ -375,19 +378,19 @@ func resourceDocumentRead(ctx context.Context, d *schema.ResourceData, meta inte { input := &ssm.DescribeDocumentPermissionInput{ Name: aws.String(d.Id()), - PermissionType: aws.String(ssm.DocumentPermissionTypeShare), + PermissionType: awstypes.DocumentPermissionTypeShare, } - output, err := conn.DescribeDocumentPermissionWithContext(ctx, input) + output, err := conn.DescribeDocumentPermission(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "reading SSM Document (%s) permissions: %s", d.Id(), err) } - if accountsIDs := aws.StringValueSlice(output.AccountIds); len(accountsIDs) > 0 { - d.Set("permissions", map[string]string{ + if accountsIDs := output.AccountIds; len(accountsIDs) > 0 { + d.Set("permissions", map[string]interface{}{ "account_ids": strings.Join(accountsIDs, ","), - names.AttrType: ssm.DocumentPermissionTypeShare, + names.AttrType: awstypes.DocumentPermissionTypeShare, }) } else { d.Set("permissions", nil) @@ -401,7 +404,7 @@ func resourceDocumentRead(ctx context.Context, d *schema.ResourceData, meta inte func resourceDocumentUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) if d.HasChange("permissions") { var oldAccountIDs, newAccountIDs itypes.Set[string] @@ -425,12 +428,12 @@ func resourceDocumentUpdate(ctx context.Context, d *schema.ResourceData, meta in for _, chunk := range slices.Chunks(newAccountIDs.Difference(oldAccountIDs), documentPermissionsBatchLimit) { input := &ssm.ModifyDocumentPermissionInput{ - AccountIdsToAdd: aws.StringSlice(chunk), + AccountIdsToAdd: chunk, Name: aws.String(d.Id()), - PermissionType: aws.String(ssm.DocumentPermissionTypeShare), + PermissionType: awstypes.DocumentPermissionTypeShare, } - _, err := conn.ModifyDocumentPermissionWithContext(ctx, input) + _, err := conn.ModifyDocumentPermission(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "modifying SSM Document (%s) permissions: %s", d.Id(), err) @@ -439,12 +442,12 @@ func resourceDocumentUpdate(ctx context.Context, d *schema.ResourceData, meta in for _, chunk := range slices.Chunks(oldAccountIDs.Difference(newAccountIDs), documentPermissionsBatchLimit) { input := &ssm.ModifyDocumentPermissionInput{ - AccountIdsToRemove: aws.StringSlice(chunk), + AccountIdsToRemove: chunk, Name: aws.String(d.Id()), - PermissionType: aws.String(ssm.DocumentPermissionTypeShare), + PermissionType: awstypes.DocumentPermissionTypeShare, } - _, err := conn.ModifyDocumentPermissionWithContext(ctx, input) + _, err := conn.ModifyDocumentPermission(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "modifying SSM Document (%s) permissions: %s", d.Id(), err) @@ -459,7 +462,7 @@ func resourceDocumentUpdate(ctx context.Context, d *schema.ResourceData, meta in if d.HasChange("content") || !isSchemaVersion1 { input := &ssm.UpdateDocumentInput{ Content: aws.String(d.Get("content").(string)), - DocumentFormat: aws.String(d.Get("document_format").(string)), + DocumentFormat: awstypes.DocumentFormat(d.Get("document_format").(string)), DocumentVersion: aws.String(d.Get("default_version").(string)), Name: aws.String(d.Id()), } @@ -478,17 +481,17 @@ func resourceDocumentUpdate(ctx context.Context, d *schema.ResourceData, meta in var defaultVersion string - output, err := conn.UpdateDocumentWithContext(ctx, input) + output, err := conn.UpdateDocument(ctx, input) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeDuplicateDocumentContent) { + if errs.IsA[*awstypes.DuplicateDocumentContent](err) { defaultVersion = d.Get("latest_version").(string) } else if err != nil { return sdkdiag.AppendErrorf(diags, "updating SSM Document (%s): %s", d.Id(), err) } else { - defaultVersion = aws.StringValue(output.DocumentDescription.DocumentVersion) + defaultVersion = aws.ToString(output.DocumentDescription.DocumentVersion) } - _, err = conn.UpdateDocumentDefaultVersionWithContext(ctx, &ssm.UpdateDocumentDefaultVersionInput{ + _, err = conn.UpdateDocumentDefaultVersion(ctx, &ssm.UpdateDocumentDefaultVersionInput{ DocumentVersion: aws.String(defaultVersion), Name: aws.String(d.Id()), }) @@ -508,7 +511,7 @@ func resourceDocumentUpdate(ctx context.Context, d *schema.ResourceData, meta in func resourceDocumentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) if v, ok := d.GetOk("permissions"); ok && len(v.(map[string]interface{})) > 0 { tfMap := flex.ExpandStringValueMap(v.(map[string]interface{})) @@ -518,12 +521,12 @@ func resourceDocumentDelete(ctx context.Context, d *schema.ResourceData, meta in for _, chunk := range chunks { input := &ssm.ModifyDocumentPermissionInput{ - AccountIdsToRemove: aws.StringSlice(chunk), + AccountIdsToRemove: chunk, Name: aws.String(d.Id()), - PermissionType: aws.String(ssm.DocumentPermissionTypeShare), + PermissionType: awstypes.DocumentPermissionTypeShare, } - _, err := conn.ModifyDocumentPermissionWithContext(ctx, input) + _, err := conn.ModifyDocumentPermission(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "modifying SSM Document (%s) permissions: %s", d.Id(), err) @@ -533,11 +536,11 @@ func resourceDocumentDelete(ctx context.Context, d *schema.ResourceData, meta in } log.Printf("[INFO] Deleting SSM Document: %s", d.Id()) - _, err := conn.DeleteDocumentWithContext(ctx, &ssm.DeleteDocumentInput{ + _, err := conn.DeleteDocument(ctx, &ssm.DeleteDocumentInput{ Name: aws.String(d.Get(names.AttrName).(string)), }) - if tfawserr.ErrMessageContains(err, ssm.ErrCodeInvalidDocument, "does not exist") { + if errs.IsAErrorMessageContains[*awstypes.InvalidDocument](err, "does not exist") { return diags } @@ -552,14 +555,14 @@ func resourceDocumentDelete(ctx context.Context, d *schema.ResourceData, meta in return diags } -func FindDocumentByName(ctx context.Context, conn *ssm.SSM, name string) (*ssm.DocumentDescription, error) { +func findDocumentByName(ctx context.Context, conn *ssm.Client, name string) (*awstypes.DocumentDescription, error) { input := &ssm.DescribeDocumentInput{ Name: aws.String(name), } - output, err := conn.DescribeDocumentWithContext(ctx, input) + output, err := conn.DescribeDocument(ctx, input) - if tfawserr.ErrMessageContains(err, ssm.ErrCodeInvalidDocument, "does not exist") { + if errs.IsAErrorMessageContains[*awstypes.InvalidDocument](err, "does not exist") { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, @@ -577,9 +580,9 @@ func FindDocumentByName(ctx context.Context, conn *ssm.SSM, name string) (*ssm.D return output.Document, nil } -func statusDocument(ctx context.Context, conn *ssm.SSM, name string) retry.StateRefreshFunc { +func statusDocument(ctx context.Context, conn *ssm.Client, name string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - output, err := FindDocumentByName(ctx, conn, name) + output, err := findDocumentByName(ctx, conn, name) if tfresource.NotFound(err) { return nil, "", nil @@ -589,25 +592,25 @@ func statusDocument(ctx context.Context, conn *ssm.SSM, name string) retry.State return nil, "", err } - return output, aws.StringValue(output.Status), nil + return output, string(output.Status), nil } } -func waitDocumentActive(ctx context.Context, conn *ssm.SSM, name string) (*ssm.DocumentDescription, error) { //nolint:unparam +func waitDocumentActive(ctx context.Context, conn *ssm.Client, name string) (*awstypes.DocumentDescription, error) { //nolint:unparam const ( timeout = 2 * time.Minute ) stateConf := &retry.StateChangeConf{ - Pending: []string{ssm.DocumentStatusCreating, ssm.DocumentStatusUpdating}, - Target: []string{ssm.DocumentStatusActive}, + Pending: enum.Slice(awstypes.DocumentStatusCreating, awstypes.DocumentStatusUpdating), + Target: enum.Slice(awstypes.DocumentStatusActive), Refresh: statusDocument(ctx, conn, name), Timeout: timeout, } outputRaw, err := stateConf.WaitForStateContext(ctx) - if output, ok := outputRaw.(*ssm.DocumentDescription); ok { - tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusInformation))) + if output, ok := outputRaw.(*awstypes.DocumentDescription); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.StatusInformation))) return output, err } @@ -615,12 +618,12 @@ func waitDocumentActive(ctx context.Context, conn *ssm.SSM, name string) (*ssm.D return nil, err } -func waitDocumentDeleted(ctx context.Context, conn *ssm.SSM, name string) (*ssm.DocumentDescription, error) { +func waitDocumentDeleted(ctx context.Context, conn *ssm.Client, name string) (*awstypes.DocumentDescription, error) { const ( timeout = 2 * time.Minute ) stateConf := &retry.StateChangeConf{ - Pending: []string{ssm.DocumentStatusDeleting}, + Pending: enum.Slice(awstypes.DocumentStatusDeleting), Target: []string{}, Refresh: statusDocument(ctx, conn, name), Timeout: timeout, @@ -628,8 +631,8 @@ func waitDocumentDeleted(ctx context.Context, conn *ssm.SSM, name string) (*ssm. outputRaw, err := stateConf.WaitForStateContext(ctx) - if output, ok := outputRaw.(*ssm.DocumentDescription); ok { - tfresource.SetLastError(err, errors.New(aws.StringValue(output.StatusInformation))) + if output, ok := outputRaw.(*awstypes.DocumentDescription); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.StatusInformation))) return output, err } @@ -637,15 +640,15 @@ func waitDocumentDeleted(ctx context.Context, conn *ssm.SSM, name string) (*ssm. return nil, err } -func expandAttachmentsSource(tfMap map[string]interface{}) *ssm.AttachmentsSource { +func expandAttachmentsSource(tfMap map[string]interface{}) *awstypes.AttachmentsSource { if tfMap == nil { return nil } - apiObject := &ssm.AttachmentsSource{} + apiObject := &awstypes.AttachmentsSource{} if v, ok := tfMap[names.AttrKey].(string); ok && v != "" { - apiObject.Key = aws.String(v) + apiObject.Key = awstypes.AttachmentsSourceKey(v) } if v, ok := tfMap[names.AttrName].(string); ok && v != "" { @@ -653,18 +656,18 @@ func expandAttachmentsSource(tfMap map[string]interface{}) *ssm.AttachmentsSourc } if v, ok := tfMap[names.AttrValues].([]interface{}); ok && len(v) > 0 { - apiObject.Values = flex.ExpandStringList(v) + apiObject.Values = flex.ExpandStringValueList(v) } return apiObject } -func expandAttachmentsSources(tfList []interface{}) []*ssm.AttachmentsSource { +func expandAttachmentsSources(tfList []interface{}) []awstypes.AttachmentsSource { if len(tfList) == 0 { return nil } - var apiObjects []*ssm.AttachmentsSource + var apiObjects []awstypes.AttachmentsSource for _, tfMapRaw := range tfList { tfMap, ok := tfMapRaw.(map[string]interface{}) @@ -679,39 +682,37 @@ func expandAttachmentsSources(tfList []interface{}) []*ssm.AttachmentsSource { continue } - apiObjects = append(apiObjects, apiObject) + apiObjects = append(apiObjects, *apiObject) } return apiObjects } -func flattenDocumentParameter(apiObject *ssm.DocumentParameter) map[string]interface{} { +func flattenDocumentParameter(apiObject *awstypes.DocumentParameter) map[string]interface{} { if apiObject == nil { return nil } - tfMap := map[string]interface{}{} + tfMap := map[string]interface{}{ + names.AttrType: apiObject.Type, + } if v := apiObject.DefaultValue; v != nil { - tfMap["default_value"] = aws.StringValue(v) + tfMap["default_value"] = aws.ToString(v) } if v := apiObject.Description; v != nil { - tfMap[names.AttrDescription] = aws.StringValue(v) + tfMap[names.AttrDescription] = aws.ToString(v) } if v := apiObject.Name; v != nil { - tfMap[names.AttrName] = aws.StringValue(v) - } - - if v := apiObject.Type; v != nil { - tfMap[names.AttrType] = aws.StringValue(v) + tfMap[names.AttrName] = aws.ToString(v) } return tfMap } -func flattenDocumentParameters(apiObjects []*ssm.DocumentParameter) []interface{} { +func flattenDocumentParameters(apiObjects []awstypes.DocumentParameter) []interface{} { if len(apiObjects) == 0 { return nil } @@ -719,11 +720,7 @@ func flattenDocumentParameters(apiObjects []*ssm.DocumentParameter) []interface{ var tfList []interface{} for _, apiObject := range apiObjects { - if apiObject == nil { - continue - } - - tfList = append(tfList, flattenDocumentParameter(apiObject)) + tfList = append(tfList, flattenDocumentParameter(&apiObject)) } return tfList diff --git a/internal/service/ssm/document_test.go b/internal/service/ssm/document_test.go index 2e0733b0824..f3187901104 100644 --- a/internal/service/ssm/document_test.go +++ b/internal/service/ssm/document_test.go @@ -622,11 +622,7 @@ func testAccCheckDocumentExists(ctx context.Context, n string) resource.TestChec return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No SSM Document ID is set") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) _, err := tfssm.FindDocumentByName(ctx, conn, rs.Primary.ID) @@ -636,7 +632,7 @@ func testAccCheckDocumentExists(ctx context.Context, n string) resource.TestChec func testAccCheckDocumentDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ssm_document" { diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index f9e8c53792c..4c657f4ad40 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -8,9 +8,11 @@ var ( ResourceActivation = resourceActivation ResourceAssociation = resourceAssociation ResourceDefaultPatchBaseline = resourceDefaultPatchBaseline + ResourceDocument = resourceDocument ResourcePatchBaseline = resourcePatchBaseline FindActivationByID = findActivationByID FindAssociationByID = findAssociationByID + FindDocumentByName = findDocumentByName FindPatchBaselineByID = findPatchBaselineByID ) diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index 3782f2819f8..482c2a1bf5e 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -70,7 +70,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Default Patch Baseline", }, { - Factory: ResourceDocument, + Factory: resourceDocument, TypeName: "aws_ssm_document", Name: "Document", Tags: &types.ServicePackageResourceTags{ From 4f146b80048c2782e73e57b3c7e58552a6ba9b51 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sun, 12 May 2024 17:37:32 -0400 Subject: [PATCH 08/34] d/aws_ssm_document: Migrate to AWS SDK for Go v2. --- internal/service/ssm/document_data_source.go | 34 ++++++++++---------- internal/service/ssm/service_package_gen.go | 3 +- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/internal/service/ssm/document_data_source.go b/internal/service/ssm/document_data_source.go index 35bf8baa68c..bd04110ac3a 100644 --- a/internal/service/ssm/document_data_source.go +++ b/internal/service/ssm/document_data_source.go @@ -5,22 +5,22 @@ package ssm import ( "context" - "fmt" "strings" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "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/sdkdiag" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_ssm_document") -func DataSourceDocument() *schema.Resource { +// @SDKDataSource("aws_ssm_document", name="Document") +func dataSourceDocument() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataDocumentRead, @@ -34,10 +34,10 @@ func DataSourceDocument() *schema.Resource { Computed: true, }, "document_format": { - Type: schema.TypeString, - Optional: true, - Default: ssm.DocumentFormatJson, - ValidateFunc: validation.StringInSlice(ssm.DocumentFormat_Values(), false), + Type: schema.TypeString, + Optional: true, + Default: awstypes.DocumentFormatJson, + ValidateDiagFunc: enum.Validate[awstypes.DocumentFormat](), }, "document_type": { Type: schema.TypeString, @@ -57,11 +57,11 @@ func DataSourceDocument() *schema.Resource { func dataDocumentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) name := d.Get(names.AttrName).(string) input := &ssm.GetDocumentInput{ - DocumentFormat: aws.String(d.Get("document_format").(string)), + DocumentFormat: awstypes.DocumentFormat(d.Get("document_format").(string)), Name: aws.String(name), } @@ -69,13 +69,13 @@ func dataDocumentRead(ctx context.Context, d *schema.ResourceData, meta interfac input.DocumentVersion = aws.String(v.(string)) } - output, err := conn.GetDocumentWithContext(ctx, input) + output, err := conn.GetDocument(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "reading SSM Document (%s): %s", name, err) + return sdkdiag.AppendErrorf(diags, "reading SSM Document (%s) content: %s", name, err) } - d.SetId(aws.StringValue(output.Name)) + d.SetId(aws.ToString(output.Name)) if !strings.HasPrefix(name, "AWS-") { arn := arn.ARN{ @@ -83,7 +83,7 @@ func dataDocumentRead(ctx context.Context, d *schema.ResourceData, meta interfac Service: "ssm", Region: meta.(*conns.AWSClient).Region, AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("document/%s", name), + Resource: "document/" + name, }.String() d.Set(names.AttrARN, arn) } else { diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index 482c2a1bf5e..3fd1e5d3290 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -25,8 +25,9 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { return []*types.ServicePackageSDKDataSource{ { - Factory: DataSourceDocument, + Factory: dataSourceDocument, TypeName: "aws_ssm_document", + Name: "Document", }, { Factory: DataSourceInstances, From 28ac9902013504bb1d7a5be66c7d7009a66a4a6e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Sun, 12 May 2024 17:42:22 -0400 Subject: [PATCH 09/34] d/aws_ssm_instances: Migrate to AWS SDK for Go v2. --- internal/service/ssm/instances_data_source.go | 59 ++++++++----------- internal/service/ssm/service_package_gen.go | 3 +- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/internal/service/ssm/instances_data_source.go b/internal/service/ssm/instances_data_source.go index 11dd8b23a2e..4be7081440b 100644 --- a/internal/service/ssm/instances_data_source.go +++ b/internal/service/ssm/instances_data_source.go @@ -6,20 +6,23 @@ package ssm import ( "context" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "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" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_ssm_instances") -func DataSourceInstances() *schema.Resource { +// @SDKDataSource("aws_ssm_instances, name="Instances") +func dataSourceInstances() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceInstancesRead, + Schema: map[string]*schema.Schema{ names.AttrFilter: { Type: schema.TypeSet, @@ -30,7 +33,6 @@ func DataSourceInstances() *schema.Resource { Type: schema.TypeString, Required: true, }, - names.AttrValues: { Type: schema.TypeList, Required: true, @@ -50,7 +52,7 @@ func DataSourceInstances() *schema.Resource { func dataSourceInstancesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) input := &ssm.DescribeInstanceInformationInput{} @@ -58,46 +60,33 @@ func dataSourceInstancesRead(ctx context.Context, d *schema.ResourceData, meta i input.Filters = expandInstanceInformationStringFilters(v.(*schema.Set).List()) } - var results []*ssm.InstanceInformation - - err := conn.DescribeInstanceInformationPagesWithContext(ctx, input, func(page *ssm.DescribeInstanceInformationOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } + var output []awstypes.InstanceInformation - for _, instanceInformation := range page.InstanceInformationList { - if instanceInformation == nil { - continue - } + pages := ssm.NewDescribeInstanceInformationPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) - results = append(results, instanceInformation) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading SSM Instances: %s", err) } - return !lastPage - }) - - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading SSM Instances: %s", err) - } - - var instanceIDs []string - - for _, r := range results { - instanceIDs = append(instanceIDs, aws.StringValue(r.InstanceId)) + output = append(output, page.InstanceInformationList...) } d.SetId(meta.(*conns.AWSClient).Region) - d.Set("ids", instanceIDs) + d.Set("ids", tfslices.ApplyToAll(output, func(v awstypes.InstanceInformation) string { + return aws.ToString(v.InstanceId) + })) return diags } -func expandInstanceInformationStringFilters(tfList []interface{}) []*ssm.InstanceInformationStringFilter { +func expandInstanceInformationStringFilters(tfList []interface{}) []awstypes.InstanceInformationStringFilter { if len(tfList) == 0 { return nil } - var apiObjects []*ssm.InstanceInformationStringFilter + var apiObjects []awstypes.InstanceInformationStringFilter for _, tfMapRaw := range tfList { tfMap, ok := tfMapRaw.(map[string]interface{}) @@ -112,25 +101,25 @@ func expandInstanceInformationStringFilters(tfList []interface{}) []*ssm.Instanc continue } - apiObjects = append(apiObjects, apiObject) + apiObjects = append(apiObjects, *apiObject) } return apiObjects } -func expandInstanceInformationStringFilter(tfMap map[string]interface{}) *ssm.InstanceInformationStringFilter { +func expandInstanceInformationStringFilter(tfMap map[string]interface{}) *awstypes.InstanceInformationStringFilter { if tfMap == nil { return nil } - apiObject := &ssm.InstanceInformationStringFilter{} + apiObject := &awstypes.InstanceInformationStringFilter{} if v, ok := tfMap[names.AttrName].(string); ok && v != "" { apiObject.Key = aws.String(v) } if v, ok := tfMap[names.AttrValues].([]interface{}); ok && len(v) > 0 { - apiObject.Values = flex.ExpandStringList(v) + apiObject.Values = flex.ExpandStringValueList(v) } return apiObject diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index 3fd1e5d3290..12b8952b615 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -30,8 +30,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Name: "Document", }, { - Factory: DataSourceInstances, + Factory: dataSourceInstances, TypeName: "aws_ssm_instances", + Name: "Instances", }, { Factory: DataSourceMaintenanceWindows, From 5dfaf3480ef74f4897030076fcb99a474f1fed8f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 08:28:42 -0400 Subject: [PATCH 10/34] r/aws_ssm_maintenance_window_target: Migrate to AWS SDK for Go v2. --- internal/service/ssm/exports_test.go | 20 +- .../service/ssm/maintenance_window_target.go | 212 +++++++++--------- .../ssm/maintenance_window_target_test.go | 80 +++---- internal/service/ssm/service_package_gen.go | 3 +- 4 files changed, 153 insertions(+), 162 deletions(-) diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index 4c657f4ad40..09f4174f84f 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -5,14 +5,16 @@ package ssm // Exports for use in tests only. var ( - ResourceActivation = resourceActivation - ResourceAssociation = resourceAssociation - ResourceDefaultPatchBaseline = resourceDefaultPatchBaseline - ResourceDocument = resourceDocument - ResourcePatchBaseline = resourcePatchBaseline + ResourceActivation = resourceActivation + ResourceAssociation = resourceAssociation + ResourceDefaultPatchBaseline = resourceDefaultPatchBaseline + ResourceDocument = resourceDocument + ResourceMaintenanceWindowTarget = resourceMaintenanceWindowTarget + ResourcePatchBaseline = resourcePatchBaseline - FindActivationByID = findActivationByID - FindAssociationByID = findAssociationByID - FindDocumentByName = findDocumentByName - FindPatchBaselineByID = findPatchBaselineByID + FindActivationByID = findActivationByID + FindAssociationByID = findAssociationByID + FindDocumentByName = findDocumentByName + FindMaintenanceWindowTargetByID = findMaintenanceWindowTargetByID + FindPatchBaselineByID = findPatchBaselineByID ) diff --git a/internal/service/ssm/maintenance_window_target.go b/internal/service/ssm/maintenance_window_target.go index ee4d551c442..e58073f258c 100644 --- a/internal/service/ssm/maintenance_window_target.go +++ b/internal/service/ssm/maintenance_window_target.go @@ -10,24 +10,28 @@ import ( "strings" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "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/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "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/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_ssm_maintenance_window_target") -func ResourceMaintenanceWindowTarget() *schema.Resource { +// @SDKResource("aws_ssm_maintenance_window_target", name="Maintenance Window Target") +func resourceMaintenanceWindowTarget() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceMaintenanceWindowTargetCreate, ReadWithoutTimeout: resourceMaintenanceWindowTargetRead, UpdateWithoutTimeout: resourceMaintenanceWindowTargetUpdate, DeleteWithoutTimeout: resourceMaintenanceWindowTargetDelete, + Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { idParts := strings.Split(d.Id(), "/") @@ -41,19 +45,23 @@ func ResourceMaintenanceWindowTarget() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "window_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + names.AttrDescription: { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(3, 128), }, - - names.AttrResourceType: { + names.AttrName: { Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, - ValidateFunc: validation.StringInSlice(ssm.MaintenanceWindowResourceType_Values(), true), + ValidateFunc: validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z_.-]{3,128}$`), "Only alphanumeric characters, hyphens, dots & underscores allowed"), + }, + "owner_information": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 128), }, - "targets": { Type: schema.TypeList, Required: true, @@ -77,25 +85,16 @@ func ResourceMaintenanceWindowTarget() *schema.Resource { }, }, }, - - names.AttrName: { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z_.-]{3,128}$`), "Only alphanumeric characters, hyphens, dots & underscores allowed"), - }, - - names.AttrDescription: { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringLenBetween(3, 128), + names.AttrResourceType: { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: enum.Validate[awstypes.MaintenanceWindowResourceType](), }, - - "owner_information": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(1, 128), + "window_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, }, }, } @@ -103,116 +102,89 @@ func ResourceMaintenanceWindowTarget() *schema.Resource { func resourceMaintenanceWindowTargetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) - - log.Printf("[INFO] Registering SSM Maintenance Window Target") + conn := meta.(*conns.AWSClient).SSMClient(ctx) - params := &ssm.RegisterTargetWithMaintenanceWindowInput{ - WindowId: aws.String(d.Get("window_id").(string)), - ResourceType: aws.String(d.Get(names.AttrResourceType).(string)), + input := &ssm.RegisterTargetWithMaintenanceWindowInput{ + ResourceType: awstypes.MaintenanceWindowResourceType(d.Get(names.AttrResourceType).(string)), Targets: expandTargets(d.Get("targets").([]interface{})), + WindowId: aws.String(d.Get("window_id").(string)), } - if v, ok := d.GetOk(names.AttrName); ok { - params.Name = aws.String(v.(string)) + if v, ok := d.GetOk(names.AttrDescription); ok { + input.Description = aws.String(v.(string)) } - if v, ok := d.GetOk(names.AttrDescription); ok { - params.Description = aws.String(v.(string)) + if v, ok := d.GetOk(names.AttrName); ok { + input.Name = aws.String(v.(string)) } if v, ok := d.GetOk("owner_information"); ok { - params.OwnerInformation = aws.String(v.(string)) + input.OwnerInformation = aws.String(v.(string)) } - resp, err := conn.RegisterTargetWithMaintenanceWindowWithContext(ctx, params) + output, err := conn.RegisterTargetWithMaintenanceWindow(ctx, input) + if err != nil { return sdkdiag.AppendErrorf(diags, "creating SSM Maintenance Window Target: %s", err) } - d.SetId(aws.StringValue(resp.WindowTargetId)) + d.SetId(aws.ToString(output.WindowTargetId)) return append(diags, resourceMaintenanceWindowTargetRead(ctx, d, meta)...) } func resourceMaintenanceWindowTargetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - windowID := d.Get("window_id").(string) - params := &ssm.DescribeMaintenanceWindowTargetsInput{ - WindowId: aws.String(windowID), - Filters: []*ssm.MaintenanceWindowFilter{ - { - Key: aws.String("WindowTargetId"), - Values: []*string{aws.String(d.Id())}, - }, - }, - } + target, err := findMaintenanceWindowTargetByID(ctx, conn, d.Id()) - resp, err := conn.DescribeMaintenanceWindowTargetsWithContext(ctx, params) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, ssm.ErrCodeDoesNotExistException) { - log.Printf("[WARN] Maintenance Window (%s) Target (%s) not found, removing from state", windowID, d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSM Maintenance Window Target %s not found, removing from state", d.Id()) d.SetId("") return diags } - if err != nil { - return sdkdiag.AppendErrorf(diags, "getting Maintenance Window (%s) Target (%s): %s", windowID, d.Id(), err) - } - - found := false - for _, t := range resp.Targets { - if aws.StringValue(t.WindowTargetId) == d.Id() { - found = true - d.Set("owner_information", t.OwnerInformation) - d.Set("window_id", t.WindowId) - d.Set(names.AttrResourceType, t.ResourceType) - d.Set(names.AttrName, t.Name) - d.Set(names.AttrDescription, t.Description) - - if err := d.Set("targets", flattenTargets(t.Targets)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting targets: %s", err) - } - - break - } + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading SSM Maintenance Window Target (%s): %s", d.Id(), err) } - if !d.IsNewResource() && !found { - log.Printf("[INFO] Maintenance Window Target not found. Removing from state") - d.SetId("") - return diags + d.Set(names.AttrDescription, target.Description) + d.Set(names.AttrName, target.Name) + d.Set("owner_information", target.OwnerInformation) + d.Set(names.AttrResourceType, target.ResourceType) + if err := d.Set("targets", flattenTargets(target.Targets)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting targets: %s", err) } + d.Set("window_id", target.WindowId) return diags } func resourceMaintenanceWindowTargetUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - log.Printf("[INFO] Updating SSM Maintenance Window Target: %s", d.Id()) - - params := &ssm.UpdateMaintenanceWindowTargetInput{ + input := &ssm.UpdateMaintenanceWindowTargetInput{ Targets: expandTargets(d.Get("targets").([]interface{})), WindowId: aws.String(d.Get("window_id").(string)), WindowTargetId: aws.String(d.Id()), } - if d.HasChange(names.AttrName) { - params.Name = aws.String(d.Get(names.AttrName).(string)) + if d.HasChange(names.AttrDescription) { + input.Description = aws.String(d.Get(names.AttrDescription).(string)) } - if d.HasChange(names.AttrDescription) { - params.Description = aws.String(d.Get(names.AttrDescription).(string)) + if d.HasChange(names.AttrName) { + input.Name = aws.String(d.Get(names.AttrName).(string)) } if d.HasChange("owner_information") { - params.OwnerInformation = aws.String(d.Get("owner_information").(string)) + input.OwnerInformation = aws.String(d.Get("owner_information").(string)) } - _, err := conn.UpdateMaintenanceWindowTargetWithContext(ctx, params) + _, err := conn.UpdateMaintenanceWindowTarget(ctx, input) + if err != nil { return sdkdiag.AppendErrorf(diags, "updating SSM Maintenance Window Target (%s): %s", d.Id(), err) } @@ -222,23 +194,61 @@ func resourceMaintenanceWindowTargetUpdate(ctx context.Context, d *schema.Resour func resourceMaintenanceWindowTargetDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - log.Printf("[INFO] Deregistering SSM Maintenance Window Target: %s", d.Id()) - - params := &ssm.DeregisterTargetFromMaintenanceWindowInput{ + log.Printf("[INFO] Deleting SSM Maintenance Window Target: %s", d.Id()) + _, err := conn.DeregisterTargetFromMaintenanceWindow(ctx, &ssm.DeregisterTargetFromMaintenanceWindowInput{ WindowId: aws.String(d.Get("window_id").(string)), WindowTargetId: aws.String(d.Id()), - } + }) - _, err := conn.DeregisterTargetFromMaintenanceWindowWithContext(ctx, params) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeDoesNotExistException) { + if errs.IsA[*awstypes.DoesNotExistException](err) { return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "deregistering SSM Maintenance Window Target (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "deleting SSM Maintenance Window Target (%s): %s", d.Id(), err) } return diags } + +func findMaintenanceWindowTargetByID(ctx context.Context, conn *ssm.Client, id string) (*awstypes.MaintenanceWindowTarget, error) { + input := &ssm.DescribeMaintenanceWindowTargetsInput{ + Filters: []awstypes.MaintenanceWindowFilter{ + { + Key: aws.String("WindowTargetId"), + Values: []string{id}, + }, + }, + } + + return findMaintenanceWindowTarget(ctx, conn, input) +} + +func findMaintenanceWindowTarget(ctx context.Context, conn *ssm.Client, input *ssm.DescribeMaintenanceWindowTargetsInput) (*awstypes.MaintenanceWindowTarget, error) { + output, err := findMaintenanceWindowTargets(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findMaintenanceWindowTargets(ctx context.Context, conn *ssm.Client, input *ssm.DescribeMaintenanceWindowTargetsInput) ([]awstypes.MaintenanceWindowTarget, error) { + var output []awstypes.MaintenanceWindowTarget + + pages := ssm.NewDescribeMaintenanceWindowTargetsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if err != nil { + return nil, err + } + + output = append(output, page.Targets...) + } + + return output, nil +} diff --git a/internal/service/ssm/maintenance_window_target_test.go b/internal/service/ssm/maintenance_window_target_test.go index 1f33fed6738..5240b401bc4 100644 --- a/internal/service/ssm/maintenance_window_target_test.go +++ b/internal/service/ssm/maintenance_window_target_test.go @@ -9,23 +9,23 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" 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" tfssm "github.com/hashicorp/terraform-provider-aws/internal/service/ssm" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccSSMMaintenanceWindowTarget_basic(t *testing.T) { ctx := acctest.Context(t) - var maint ssm.MaintenanceWindowTarget + var maint awstypes.MaintenanceWindowTarget rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_maintenance_window_target.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMServiceID), @@ -45,7 +45,7 @@ func TestAccSSMMaintenanceWindowTarget_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "targets.1.values.1", "acceptance_test2"), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, names.AttrDescription, "This resource is for test purpose only"), - resource.TestCheckResourceAttr(resourceName, names.AttrResourceType, ssm.MaintenanceWindowResourceTypeInstance), + resource.TestCheckResourceAttr(resourceName, names.AttrResourceType, string(awstypes.MaintenanceWindowResourceTypeInstance)), ), }, { @@ -60,7 +60,8 @@ func TestAccSSMMaintenanceWindowTarget_basic(t *testing.T) { func TestAccSSMMaintenanceWindowTarget_noNameOrDescription(t *testing.T) { ctx := acctest.Context(t) - var maint ssm.MaintenanceWindowTarget + var maint awstypes.MaintenanceWindowTarget + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_maintenance_window_target.test" resource.ParallelTest(t, resource.TestCase{ @@ -95,6 +96,7 @@ func TestAccSSMMaintenanceWindowTarget_noNameOrDescription(t *testing.T) { func TestAccSSMMaintenanceWindowTarget_validation(t *testing.T) { ctx := acctest.Context(t) name := sdkacctest.RandString(10) + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMServiceID), @@ -119,9 +121,10 @@ func TestAccSSMMaintenanceWindowTarget_validation(t *testing.T) { func TestAccSSMMaintenanceWindowTarget_update(t *testing.T) { ctx := acctest.Context(t) - var maint ssm.MaintenanceWindowTarget + var maint awstypes.MaintenanceWindowTarget rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_maintenance_window_target.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMServiceID), @@ -176,9 +179,10 @@ func TestAccSSMMaintenanceWindowTarget_update(t *testing.T) { func TestAccSSMMaintenanceWindowTarget_resourceGroup(t *testing.T) { ctx := acctest.Context(t) - var maint ssm.MaintenanceWindowTarget + var maint awstypes.MaintenanceWindowTarget rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_maintenance_window_target.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMServiceID), @@ -197,7 +201,7 @@ func TestAccSSMMaintenanceWindowTarget_resourceGroup(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "targets.1.values.0", "resource-group-name"), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, names.AttrDescription, "This resource is for test purpose only"), - resource.TestCheckResourceAttr(resourceName, names.AttrResourceType, ssm.MaintenanceWindowResourceTypeResourceGroup), + resource.TestCheckResourceAttr(resourceName, names.AttrResourceType, string(awstypes.MaintenanceWindowResourceTypeResourceGroup)), ), }, { @@ -212,9 +216,10 @@ func TestAccSSMMaintenanceWindowTarget_resourceGroup(t *testing.T) { func TestAccSSMMaintenanceWindowTarget_disappears(t *testing.T) { ctx := acctest.Context(t) - var maint ssm.MaintenanceWindowTarget + var maint awstypes.MaintenanceWindowTarget rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_maintenance_window_target.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMServiceID), @@ -235,9 +240,10 @@ func TestAccSSMMaintenanceWindowTarget_disappears(t *testing.T) { func TestAccSSMMaintenanceWindowTarget_Disappears_window(t *testing.T) { ctx := acctest.Context(t) - var maint ssm.MaintenanceWindowTarget + var maint awstypes.MaintenanceWindowTarget rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_maintenance_window_target.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.SSMServiceID), @@ -256,75 +262,47 @@ func TestAccSSMMaintenanceWindowTarget_Disappears_window(t *testing.T) { }) } -func testAccCheckMaintenanceWindowTargetExists(ctx context.Context, n string, mWindTarget *ssm.MaintenanceWindowTarget) resource.TestCheckFunc { +func testAccCheckMaintenanceWindowTargetExists(ctx context.Context, n string, v *awstypes.MaintenanceWindowTarget) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No SSM Maintenance Window Target Window ID is set") - } + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + output, err := tfssm.FindMaintenanceWindowTargetByID(ctx, conn, rs.Primary.ID) - resp, err := conn.DescribeMaintenanceWindowTargetsWithContext(ctx, &ssm.DescribeMaintenanceWindowTargetsInput{ - WindowId: aws.String(rs.Primary.Attributes["window_id"]), - Filters: []*ssm.MaintenanceWindowFilter{ - { - Key: aws.String("WindowTargetId"), - Values: []*string{aws.String(rs.Primary.ID)}, - }, - }, - }) if err != nil { return err } - for _, i := range resp.Targets { - if aws.StringValue(i.WindowTargetId) == rs.Primary.ID { - *mWindTarget = *resp.Targets[0] - return nil - } - } + *v = *output - return fmt.Errorf("No AWS SSM Maintenance window target found") + return nil } } func testAccCheckMaintenanceWindowTargetDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ssm_maintenance_window_target" { continue } - out, err := conn.DescribeMaintenanceWindowTargetsWithContext(ctx, &ssm.DescribeMaintenanceWindowTargetsInput{ - WindowId: aws.String(rs.Primary.Attributes["window_id"]), - Filters: []*ssm.MaintenanceWindowFilter{ - { - Key: aws.String("WindowTargetId"), - Values: []*string{aws.String(rs.Primary.ID)}, - }, - }, - }) + _, err := tfssm.FindMaintenanceWindowTargetByID(ctx, conn, rs.Primary.ID) - if err != nil { - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeDoesNotExistException) { - continue - } - return err + if tfresource.NotFound(err) { + continue } - if len(out.Targets) > 0 { - return fmt.Errorf("Expected AWS SSM Maintenance Target to be gone, but was still found") + if err != nil { + return err } - return nil + return fmt.Errorf("SSM Activation %s still exists", rs.Primary.ID) } return nil diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index 12b8952b615..e64ca585b6c 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -90,8 +90,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceMaintenanceWindowTarget, + Factory: resourceMaintenanceWindowTarget, TypeName: "aws_ssm_maintenance_window_target", + Name: "Maintenance Window Target", }, { Factory: ResourceMaintenanceWindowTask, From a023f1129d5300894bb31d20d2c05fdb18c1a9eb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 09:23:38 -0400 Subject: [PATCH 11/34] r/aws_ssm_maintenance_window_task: Migrate to AWS SDK for Go v2. --- .changelog/#####.txt | 3 + internal/service/ssm/exports_test.go | 12 +- .../service/ssm/maintenance_window_task.go | 919 +++++++++--------- .../ssm/maintenance_window_task_test.go | 82 +- internal/service/ssm/service_package_gen.go | 3 +- 5 files changed, 499 insertions(+), 520 deletions(-) create mode 100644 .changelog/#####.txt diff --git a/.changelog/#####.txt b/.changelog/#####.txt new file mode 100644 index 00000000000..c7508a3f007 --- /dev/null +++ b/.changelog/#####.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_ssm_maintenance_window_task: Add plan-time validation of `task_arn` +``` \ No newline at end of file diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index 09f4174f84f..fa67169f463 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -10,11 +10,13 @@ var ( ResourceDefaultPatchBaseline = resourceDefaultPatchBaseline ResourceDocument = resourceDocument ResourceMaintenanceWindowTarget = resourceMaintenanceWindowTarget + ResourceMaintenanceWindowTask = resourceMaintenanceWindowTask ResourcePatchBaseline = resourcePatchBaseline - FindActivationByID = findActivationByID - FindAssociationByID = findAssociationByID - FindDocumentByName = findDocumentByName - FindMaintenanceWindowTargetByID = findMaintenanceWindowTargetByID - FindPatchBaselineByID = findPatchBaselineByID + FindActivationByID = findActivationByID + FindAssociationByID = findAssociationByID + FindDocumentByName = findDocumentByName + FindMaintenanceWindowTargetByID = findMaintenanceWindowTargetByID + FindMaintenanceWindowTaskByTwoPartKey = findMaintenanceWindowTaskByTwoPartKey + FindPatchBaselineByID = findPatchBaselineByID ) diff --git a/internal/service/ssm/maintenance_window_task.go b/internal/service/ssm/maintenance_window_task.go index d4186836de3..cce8991d877 100644 --- a/internal/service/ssm/maintenance_window_task.go +++ b/internal/service/ssm/maintenance_window_task.go @@ -7,31 +7,37 @@ import ( "context" "fmt" "log" - "sort" + "slices" "strings" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/service/ssm" - "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/aws/arn" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/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/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" + tfmaps "github.com/hashicorp/terraform-provider-aws/internal/maps" + "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_ssm_maintenance_window_task") -func ResourceMaintenanceWindowTask() *schema.Resource { +// @SDKResource("aws_ssm_maintenance_window_task", name="Maintenance Window Task") +func resourceMaintenanceWindowTask() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceMaintenanceWindowTaskCreate, ReadWithoutTimeout: resourceMaintenanceWindowTaskRead, UpdateWithoutTimeout: resourceMaintenanceWindowTaskUpdate, DeleteWithoutTimeout: resourceMaintenanceWindowTaskDelete, + Importer: &schema.ResourceImporter{ StateContext: resourceMaintenanceWindowTaskImport, }, @@ -41,55 +47,45 @@ func ResourceMaintenanceWindowTask() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "window_task_id": { - Type: schema.TypeString, - Computed: true, - }, - "window_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "cutoff_behavior": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.MaintenanceWindowTaskCutoffBehavior](), + }, + names.AttrDescription: { Type: schema.TypeString, Optional: true, - ValidateFunc: validation.StringInSlice(ssm.MaintenanceWindowTaskCutoffBehavior_Values(), false), + ValidateFunc: validation.StringLenBetween(1, 128), }, - "max_concurrency": { Type: schema.TypeString, Optional: true, Computed: true, ValidateFunc: validation.StringMatch(regexache.MustCompile(`^([1-9][0-9]*|[1-9][0-9]%|[1-9]%|100%)$`), "must be a number without leading zeros or a percentage between 1% and 100% without leading zeros and ending with the percentage symbol"), }, - "max_errors": { Type: schema.TypeString, Optional: true, Computed: true, ValidateFunc: validation.StringMatch(regexache.MustCompile(`^([1-9][0-9]*|[0]|[1-9][0-9]%|[0-9]%|100%)$`), "must be zero, a number without leading zeros, or a percentage between 1% and 100% without leading zeros and ending with the percentage symbol"), }, - - "task_type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice(ssm.MaintenanceWindowTaskType_Values(), false), - }, - - "task_arn": { + names.AttrName: { Type: schema.TypeString, - Required: true, + Optional: true, + ValidateFunc: validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z_.-]{3,128}$`), + "Only alphanumeric characters, hyphens, dots & underscores allowed."), + }, + names.AttrPriority: { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(0), }, - "service_role_arn": { Type: schema.TypeString, Optional: true, Computed: true, ValidateFunc: verify.ValidARN, }, - "targets": { Type: schema.TypeList, Optional: true, @@ -109,26 +105,11 @@ func ResourceMaintenanceWindowTask() *schema.Resource { }, }, }, - - names.AttrName: { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringMatch(regexache.MustCompile(`^[0-9A-Za-z_.-]{3,128}$`), - "Only alphanumeric characters, hyphens, dots & underscores allowed."), - }, - - names.AttrDescription: { + "task_arn": { Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(1, 128), - }, - - names.AttrPriority: { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntAtLeast(0), + Required: true, + ValidateFunc: verify.ValidARN, }, - "task_invocation_parameters": { Type: schema.TypeList, Optional: true, @@ -155,7 +136,6 @@ func ResourceMaintenanceWindowTask() *schema.Resource { Type: schema.TypeString, Required: true, }, - names.AttrValues: { Type: schema.TypeList, Required: true, @@ -167,7 +147,6 @@ func ResourceMaintenanceWindowTask() *schema.Resource { }, }, }, - "lambda_parameters": { Type: schema.TypeList, Optional: true, @@ -179,14 +158,12 @@ func ResourceMaintenanceWindowTask() *schema.Resource { Optional: true, ValidateFunc: validation.StringLenBetween(1, 8000), }, - "payload": { Type: schema.TypeString, Optional: true, Sensitive: true, ValidateFunc: validation.StringLenBetween(0, 4096), }, - "qualifier": { Type: schema.TypeString, Optional: true, @@ -195,36 +172,50 @@ func ResourceMaintenanceWindowTask() *schema.Resource { }, }, }, - "run_command_parameters": { Type: schema.TypeList, Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "cloudwatch_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cloudwatch_log_group_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "cloudwatch_output_enabled": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, "comment": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringLenBetween(0, 100), }, - "document_hash": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringLenBetween(0, 256), }, - "document_hash_type": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(ssm.DocumentHashType_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.DocumentHashType](), }, "document_version": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringMatch(regexache.MustCompile(`([$]LATEST|[$]DEFAULT|^[1-9][0-9]*$)`), "must be $DEFAULT, $LATEST, or a version number"), }, - "notification_config": { Type: schema.TypeList, Optional: true, @@ -236,35 +227,30 @@ func ResourceMaintenanceWindowTask() *schema.Resource { Optional: true, ValidateFunc: verify.ValidARN, }, - "notification_events": { Type: schema.TypeList, Optional: true, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice(ssm.NotificationEvent_Values(), false), + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[awstypes.NotificationEvent](), }, }, - "notification_type": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(ssm.NotificationType_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.NotificationType](), }, }, }, }, - "output_s3_bucket": { Type: schema.TypeString, Optional: true, }, - "output_s3_key_prefix": { Type: schema.TypeString, Optional: true, }, - names.AttrParameter: { Type: schema.TypeSet, Optional: true, @@ -274,7 +260,6 @@ func ResourceMaintenanceWindowTask() *schema.Resource { Type: schema.TypeString, Required: true, }, - names.AttrValues: { Type: schema.TypeList, Required: true, @@ -283,40 +268,19 @@ func ResourceMaintenanceWindowTask() *schema.Resource { }, }, }, - "service_role_arn": { Type: schema.TypeString, Optional: true, ValidateFunc: verify.ValidARN, }, - "timeout_seconds": { Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(30, 2592000), }, - "cloudwatch_config": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "cloudwatch_log_group_name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "cloudwatch_output_enabled": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, }, }, }, - "step_functions_parameters": { Type: schema.TypeList, Optional: true, @@ -329,7 +293,6 @@ func ResourceMaintenanceWindowTask() *schema.Resource { Sensitive: true, ValidateFunc: validation.StringLenBetween(0, 4096), }, - names.AttrName: { Type: schema.TypeString, Optional: true, @@ -341,541 +304,569 @@ func ResourceMaintenanceWindowTask() *schema.Resource { }, }, }, + "task_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: enum.Validate[awstypes.MaintenanceWindowTaskType](), + }, + "window_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "window_task_id": { + Type: schema.TypeString, + Computed: true, + }, }, } } -func expandTaskInvocationParameters(config []interface{}) *ssm.MaintenanceWindowTaskInvocationParameters { - if len(config) == 0 || config[0] == nil { - return nil - } +func resourceMaintenanceWindowTaskCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SSMClient(ctx) - params := &ssm.MaintenanceWindowTaskInvocationParameters{} - for _, v := range config { - paramConfig := v.(map[string]interface{}) - if attr, ok := paramConfig["automation_parameters"]; ok && len(attr.([]interface{})) > 0 && attr.([]interface{})[0] != nil { - params.Automation = expandTaskInvocationAutomationParameters(attr.([]interface{})) - } - if attr, ok := paramConfig["lambda_parameters"]; ok && len(attr.([]interface{})) > 0 && attr.([]interface{})[0] != nil { - params.Lambda = expandTaskInvocationLambdaParameters(attr.([]interface{})) - } - if attr, ok := paramConfig["run_command_parameters"]; ok && len(attr.([]interface{})) > 0 && attr.([]interface{})[0] != nil { - params.RunCommand = expandTaskInvocationRunCommandParameters(attr.([]interface{})) - } - if attr, ok := paramConfig["step_functions_parameters"]; ok && len(attr.([]interface{})) > 0 && attr.([]interface{})[0] != nil { - params.StepFunctions = expandTaskInvocationStepFunctionsParameters(attr.([]interface{})) - } + input := &ssm.RegisterTaskWithMaintenanceWindowInput{ + TaskArn: aws.String(d.Get("task_arn").(string)), + TaskType: awstypes.MaintenanceWindowTaskType(d.Get("task_type").(string)), + WindowId: aws.String(d.Get("window_id").(string)), } - return params -} -func flattenTaskInvocationParameters(parameters *ssm.MaintenanceWindowTaskInvocationParameters) []interface{} { - result := make(map[string]interface{}) - if parameters.Automation != nil { - result["automation_parameters"] = flattenTaskInvocationAutomationParameters(parameters.Automation) + if v, ok := d.GetOk("cutoff_behavior"); ok { + input.CutoffBehavior = awstypes.MaintenanceWindowTaskCutoffBehavior(v.(string)) } - if parameters.Lambda != nil { - result["lambda_parameters"] = flattenTaskInvocationLambdaParameters(parameters.Lambda) + if v, ok := d.GetOk(names.AttrDescription); ok { + input.Description = aws.String(v.(string)) } - if parameters.RunCommand != nil { - result["run_command_parameters"] = flattenTaskInvocationRunCommandParameters(parameters.RunCommand) + if v, ok := d.GetOk("max_concurrency"); ok { + input.MaxConcurrency = aws.String(v.(string)) } - if parameters.StepFunctions != nil { - result["step_functions_parameters"] = flattenTaskInvocationStepFunctionsParameters(parameters.StepFunctions) + if v, ok := d.GetOk("max_errors"); ok { + input.MaxErrors = aws.String(v.(string)) } - return []interface{}{result} -} + if v, ok := d.GetOk(names.AttrName); ok { + input.Name = aws.String(v.(string)) + } -func expandTaskInvocationAutomationParameters(config []interface{}) *ssm.MaintenanceWindowAutomationParameters { - if len(config) == 0 || config[0] == nil { - return nil + if v, ok := d.GetOk(names.AttrPriority); ok { + input.Priority = aws.Int32(int32(v.(int))) } - params := &ssm.MaintenanceWindowAutomationParameters{} - configParam := config[0].(map[string]interface{}) - if attr, ok := configParam["document_version"]; ok && len(attr.(string)) != 0 { - params.DocumentVersion = aws.String(attr.(string)) + if v, ok := d.GetOk("service_role_arn"); ok { + input.ServiceRoleArn = aws.String(v.(string)) } - if attr, ok := configParam[names.AttrParameter]; ok && len(attr.(*schema.Set).List()) > 0 { - params.Parameters = expandTaskInvocationCommonParameters(attr.(*schema.Set).List()) + + if v, ok := d.GetOk("targets"); ok { + input.Targets = expandTargets(v.([]interface{})) } - return params -} + if v, ok := d.GetOk("task_invocation_parameters"); ok { + input.TaskInvocationParameters = expandTaskInvocationParameters(v.([]interface{})) + } -func flattenTaskInvocationAutomationParameters(parameters *ssm.MaintenanceWindowAutomationParameters) []interface{} { - result := make(map[string]interface{}) + output, err := conn.RegisterTaskWithMaintenanceWindow(ctx, input) - if parameters.DocumentVersion != nil { - result["document_version"] = aws.StringValue(parameters.DocumentVersion) - } - if parameters.Parameters != nil { - result[names.AttrParameter] = flattenTaskInvocationCommonParameters(parameters.Parameters) + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating SSM Maintenance Window Task: %s", err) } - return []interface{}{result} + d.SetId(aws.ToString(output.WindowTaskId)) + + return append(diags, resourceMaintenanceWindowTaskRead(ctx, d, meta)...) } -func expandTaskInvocationLambdaParameters(config []interface{}) *ssm.MaintenanceWindowLambdaParameters { - if len(config) == 0 || config[0] == nil { - return nil - } +func resourceMaintenanceWindowTaskRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SSMClient(ctx) + + output, err := findMaintenanceWindowTaskByTwoPartKey(ctx, conn, d.Id(), d.Get("window_task_id").(string)) - params := &ssm.MaintenanceWindowLambdaParameters{} - configParam := config[0].(map[string]interface{}) - if attr, ok := configParam["client_context"]; ok && len(attr.(string)) != 0 { - params.ClientContext = aws.String(attr.(string)) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSM Maintenance Window Task %s not found, removing from state", d.Id()) + d.SetId("") + return diags } - if attr, ok := configParam["payload"]; ok && len(attr.(string)) != 0 { - params.Payload = []byte(attr.(string)) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading SSM Maintenance Window Task (%s): %s", d.Id(), err) } - if attr, ok := configParam["qualifier"]; ok && len(attr.(string)) != 0 { - params.Qualifier = aws.String(attr.(string)) + + windowTaskID := aws.ToString(output.WindowTaskId) + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: "ssm", + Region: meta.(*conns.AWSClient).Region, + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: "windowtask/" + windowTaskID, + }.String() + d.Set(names.AttrARN, arn) + d.Set("cutoff_behavior", output.CutoffBehavior) + d.Set(names.AttrDescription, output.Description) + d.Set("max_concurrency", output.MaxConcurrency) + d.Set("max_errors", output.MaxErrors) + d.Set(names.AttrName, output.Name) + d.Set(names.AttrPriority, output.Priority) + d.Set("service_role_arn", output.ServiceRoleArn) + if err := d.Set("targets", flattenTargets(output.Targets)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting targets: %s", err) + } + d.Set("task_arn", output.TaskArn) + if output.TaskInvocationParameters != nil { + if err := d.Set("task_invocation_parameters", flattenTaskInvocationParameters(output.TaskInvocationParameters)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting task_invocation_parameters: %s", err) + } } - return params + d.Set("task_type", output.TaskType) + d.Set("window_id", output.WindowId) + d.Set("window_task_id", windowTaskID) + + return diags } -func flattenTaskInvocationLambdaParameters(parameters *ssm.MaintenanceWindowLambdaParameters) []interface{} { - result := make(map[string]interface{}) +func resourceMaintenanceWindowTaskUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SSMClient(ctx) - if parameters.ClientContext != nil { - result["client_context"] = aws.StringValue(parameters.ClientContext) - } - if parameters.Payload != nil { - result["payload"] = string(parameters.Payload) - } - if parameters.Qualifier != nil { - result["qualifier"] = aws.StringValue(parameters.Qualifier) + input := &ssm.UpdateMaintenanceWindowTaskInput{ + Priority: aws.Int32(int32(d.Get(names.AttrPriority).(int))), + Replace: aws.Bool(true), + TaskArn: aws.String(d.Get("task_arn").(string)), + WindowId: aws.String(d.Get("window_id").(string)), + WindowTaskId: aws.String(d.Id()), } - return []interface{}{result} -} -func expandTaskInvocationRunCommandParameters(config []interface{}) *ssm.MaintenanceWindowRunCommandParameters { - if len(config) == 0 || config[0] == nil { - return nil + if v, ok := d.GetOk("cutoff_behavior"); ok { + input.CutoffBehavior = awstypes.MaintenanceWindowTaskCutoffBehavior(v.(string)) } - params := &ssm.MaintenanceWindowRunCommandParameters{} - configParam := config[0].(map[string]interface{}) - if attr, ok := configParam["comment"]; ok && len(attr.(string)) != 0 { - params.Comment = aws.String(attr.(string)) - } - if attr, ok := configParam["document_hash"]; ok && len(attr.(string)) != 0 { - params.DocumentHash = aws.String(attr.(string)) - } - if attr, ok := configParam["document_hash_type"]; ok && len(attr.(string)) != 0 { - params.DocumentHashType = aws.String(attr.(string)) - } - if attr, ok := configParam["document_version"]; ok && len(attr.(string)) != 0 { - params.DocumentVersion = aws.String(attr.(string)) + if v, ok := d.GetOk(names.AttrDescription); ok { + input.Description = aws.String(v.(string)) } - if attr, ok := configParam["notification_config"]; ok && len(attr.([]interface{})) > 0 { - params.NotificationConfig = expandTaskInvocationRunCommandParametersNotificationConfig(attr.([]interface{})) + + if v, ok := d.GetOk("max_concurrency"); ok { + input.MaxConcurrency = aws.String(v.(string)) } - if attr, ok := configParam["output_s3_bucket"]; ok && len(attr.(string)) != 0 { - params.OutputS3BucketName = aws.String(attr.(string)) + + if v, ok := d.GetOk("max_errors"); ok { + input.MaxErrors = aws.String(v.(string)) } - if attr, ok := configParam["output_s3_key_prefix"]; ok && len(attr.(string)) != 0 { - params.OutputS3KeyPrefix = aws.String(attr.(string)) + + if v, ok := d.GetOk(names.AttrName); ok { + input.Name = aws.String(v.(string)) } - if attr, ok := configParam[names.AttrParameter]; ok && len(attr.(*schema.Set).List()) > 0 { - params.Parameters = expandTaskInvocationCommonParameters(attr.(*schema.Set).List()) + + if v, ok := d.GetOk("service_role_arn"); ok { + input.ServiceRoleArn = aws.String(v.(string)) } - if attr, ok := configParam["service_role_arn"]; ok && len(attr.(string)) != 0 { - params.ServiceRoleArn = aws.String(attr.(string)) + + if v, ok := d.GetOk("task_invocation_parameters"); ok { + input.TaskInvocationParameters = expandTaskInvocationParameters(v.([]interface{})) } - if attr, ok := configParam["timeout_seconds"]; ok && attr.(int) != 0 { - params.TimeoutSeconds = aws.Int64(int64(attr.(int))) + + if v, ok := d.GetOk("targets"); ok { + input.Targets = expandTargets(v.([]interface{})) + } else { + input.MaxConcurrency = nil + input.MaxErrors = nil } - if attr, ok := configParam["cloudwatch_config"]; ok && len(attr.([]interface{})) > 0 { - params.CloudWatchOutputConfig = expandTaskInvocationRunCommandParametersCloudWatchConfig(attr.([]interface{})) + _, err := conn.UpdateMaintenanceWindowTask(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating Maintenance Window Task (%s): %s", d.Id(), err) } - return params + + return append(diags, resourceMaintenanceWindowTaskRead(ctx, d, meta)...) } -func flattenTaskInvocationRunCommandParameters(parameters *ssm.MaintenanceWindowRunCommandParameters) []interface{} { - result := make(map[string]interface{}) +func resourceMaintenanceWindowTaskDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SSMClient(ctx) - if parameters.Comment != nil { - result["comment"] = aws.StringValue(parameters.Comment) - } - if parameters.DocumentHash != nil { - result["document_hash"] = aws.StringValue(parameters.DocumentHash) - } - if parameters.DocumentHashType != nil { - result["document_hash_type"] = aws.StringValue(parameters.DocumentHashType) - } - if parameters.DocumentVersion != nil { - result["document_version"] = aws.StringValue(parameters.DocumentVersion) - } - if parameters.NotificationConfig != nil { - result["notification_config"] = flattenTaskInvocationRunCommandParametersNotificationConfig(parameters.NotificationConfig) - } - if parameters.OutputS3BucketName != nil { - result["output_s3_bucket"] = aws.StringValue(parameters.OutputS3BucketName) - } - if parameters.OutputS3KeyPrefix != nil { - result["output_s3_key_prefix"] = aws.StringValue(parameters.OutputS3KeyPrefix) - } - if parameters.Parameters != nil { - result[names.AttrParameter] = flattenTaskInvocationCommonParameters(parameters.Parameters) - } - if parameters.ServiceRoleArn != nil { - result["service_role_arn"] = aws.StringValue(parameters.ServiceRoleArn) - } - if parameters.TimeoutSeconds != nil { - result["timeout_seconds"] = aws.Int64Value(parameters.TimeoutSeconds) + log.Printf("[INFO] Deleting SSM Maintenance Window Task: %s", d.Id()) + _, err := conn.DeregisterTaskFromMaintenanceWindow(ctx, &ssm.DeregisterTaskFromMaintenanceWindowInput{ + WindowId: aws.String(d.Get("window_id").(string)), + WindowTaskId: aws.String(d.Id()), + }) + + if errs.IsA[*awstypes.DoesNotExistException](err) { + return diags } - if parameters.CloudWatchOutputConfig != nil { - result["cloudwatch_config"] = flattenTaskInvocationRunCommandParametersCloudWatchConfig(parameters.CloudWatchOutputConfig) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting SSM Maintenance Window Task (%s): %s", d.Id(), err) } - return []interface{}{result} + return diags } -func expandTaskInvocationStepFunctionsParameters(config []interface{}) *ssm.MaintenanceWindowStepFunctionsParameters { - if len(config) == 0 || config[0] == nil { - return nil +func resourceMaintenanceWindowTaskImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + idParts := strings.SplitN(d.Id(), "/", 2) + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return nil, fmt.Errorf("unexpected format of ID (%q), expected /", d.Id()) } - params := &ssm.MaintenanceWindowStepFunctionsParameters{} - configParam := config[0].(map[string]interface{}) + windowID := idParts[0] + windowTaskID := idParts[1] - if attr, ok := configParam["input"]; ok && len(attr.(string)) != 0 { - params.Input = aws.String(attr.(string)) - } - if attr, ok := configParam[names.AttrName]; ok && len(attr.(string)) != 0 { - params.Name = aws.String(attr.(string)) - } + d.Set("window_id", windowID) + d.SetId(windowTaskID) - return params + return []*schema.ResourceData{d}, nil } -func flattenTaskInvocationStepFunctionsParameters(parameters *ssm.MaintenanceWindowStepFunctionsParameters) []interface{} { - result := make(map[string]interface{}) +func findMaintenanceWindowTaskByTwoPartKey(ctx context.Context, conn *ssm.Client, windowID, windowTaskID string) (*ssm.GetMaintenanceWindowTaskOutput, error) { + input := &ssm.GetMaintenanceWindowTaskInput{ + WindowId: aws.String(windowID), + WindowTaskId: aws.String(windowTaskID), + } + + output, err := conn.GetMaintenanceWindowTask(ctx, input) + + if errs.IsA[*awstypes.DoesNotExistException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } - if parameters.Input != nil { - result["input"] = aws.StringValue(parameters.Input) + if err != nil { + return nil, err } - if parameters.Name != nil { - result[names.AttrName] = aws.StringValue(parameters.Name) + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) } - return []interface{}{result} + + return output, nil } -func expandTaskInvocationRunCommandParametersNotificationConfig(config []interface{}) *ssm.NotificationConfig { - if len(config) == 0 || config[0] == nil { +func expandTaskInvocationParameters(tfList []interface{}) *awstypes.MaintenanceWindowTaskInvocationParameters { + if len(tfList) == 0 || tfList[0] == nil { return nil } - params := &ssm.NotificationConfig{} - configParam := config[0].(map[string]interface{}) + apiObject := &awstypes.MaintenanceWindowTaskInvocationParameters{} - if attr, ok := configParam["notification_arn"]; ok && len(attr.(string)) != 0 { - params.NotificationArn = aws.String(attr.(string)) - } - if attr, ok := configParam["notification_events"]; ok && len(attr.([]interface{})) > 0 { - params.NotificationEvents = flex.ExpandStringList(attr.([]interface{})) - } - if attr, ok := configParam["notification_type"]; ok && len(attr.(string)) != 0 { - params.NotificationType = aws.String(attr.(string)) + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) + if v, ok := tfMap["automation_parameters"]; ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + apiObject.Automation = expandTaskInvocationAutomationParameters(v.([]interface{})) + } + if v, ok := tfMap["lambda_parameters"]; ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + apiObject.Lambda = expandTaskInvocationLambdaParameters(v.([]interface{})) + } + if v, ok := tfMap["run_command_parameters"]; ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + apiObject.RunCommand = expandTaskInvocationRunCommandParameters(v.([]interface{})) + } + if v, ok := tfMap["step_functions_parameters"]; ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + apiObject.StepFunctions = expandTaskInvocationStepFunctionsParameters(v.([]interface{})) + } } - return params + return apiObject } -func flattenTaskInvocationRunCommandParametersNotificationConfig(config *ssm.NotificationConfig) []interface{} { - result := make(map[string]interface{}) +func flattenTaskInvocationParameters(apiObject *awstypes.MaintenanceWindowTaskInvocationParameters) []interface{} { + tfMap := make(map[string]interface{}) + + if apiObject.Automation != nil { + tfMap["automation_parameters"] = flattenTaskInvocationAutomationParameters(apiObject.Automation) + } - if config.NotificationArn != nil { - result["notification_arn"] = aws.StringValue(config.NotificationArn) + if apiObject.Lambda != nil { + tfMap["lambda_parameters"] = flattenTaskInvocationLambdaParameters(apiObject.Lambda) } - if config.NotificationEvents != nil { - result["notification_events"] = flex.FlattenStringList(config.NotificationEvents) + + if apiObject.RunCommand != nil { + tfMap["run_command_parameters"] = flattenTaskInvocationRunCommandParameters(apiObject.RunCommand) } - if config.NotificationType != nil { - result["notification_type"] = aws.StringValue(config.NotificationType) + + if apiObject.StepFunctions != nil { + tfMap["step_functions_parameters"] = flattenTaskInvocationStepFunctionsParameters(apiObject.StepFunctions) } - return []interface{}{result} + return []interface{}{tfMap} } -func expandTaskInvocationRunCommandParametersCloudWatchConfig(config []interface{}) *ssm.CloudWatchOutputConfig { - if len(config) == 0 || config[0] == nil { +func expandTaskInvocationAutomationParameters(tfList []interface{}) *awstypes.MaintenanceWindowAutomationParameters { + if len(tfList) == 0 || tfList[0] == nil { return nil } - params := &ssm.CloudWatchOutputConfig{} - configParam := config[0].(map[string]interface{}) + apiObject := &awstypes.MaintenanceWindowAutomationParameters{} + tfMap := tfList[0].(map[string]interface{}) - if attr, ok := configParam["cloudwatch_log_group_name"]; ok && len(attr.(string)) != 0 { - params.CloudWatchLogGroupName = aws.String(attr.(string)) + if v, ok := tfMap["document_version"]; ok && len(v.(string)) != 0 { + apiObject.DocumentVersion = aws.String(v.(string)) } - if attr, ok := configParam["cloudwatch_output_enabled"]; ok { - params.CloudWatchOutputEnabled = aws.Bool(attr.(bool)) + if v, ok := tfMap[names.AttrParameter]; ok && len(v.(*schema.Set).List()) > 0 { + apiObject.Parameters = expandTaskInvocationCommonParameters(v.(*schema.Set).List()) } - return params + return apiObject } -func flattenTaskInvocationRunCommandParametersCloudWatchConfig(config *ssm.CloudWatchOutputConfig) []interface{} { - result := make(map[string]interface{}) +func flattenTaskInvocationAutomationParameters(apiObject *awstypes.MaintenanceWindowAutomationParameters) []interface{} { + tfMap := make(map[string]interface{}) - if config.CloudWatchLogGroupName != nil { - result["cloudwatch_log_group_name"] = aws.StringValue(config.CloudWatchLogGroupName) + if apiObject.DocumentVersion != nil { + tfMap["document_version"] = aws.ToString(apiObject.DocumentVersion) } - if config.CloudWatchOutputEnabled != nil { - result["cloudwatch_output_enabled"] = aws.BoolValue(config.CloudWatchOutputEnabled) + if apiObject.Parameters != nil { + tfMap[names.AttrParameter] = flattenTaskInvocationCommonParameters(apiObject.Parameters) } - return []interface{}{result} + return []interface{}{tfMap} } -func expandTaskInvocationCommonParameters(config []interface{}) map[string][]*string { - if len(config) == 0 || config[0] == nil { +func expandTaskInvocationLambdaParameters(tfList []interface{}) *awstypes.MaintenanceWindowLambdaParameters { + if len(tfList) == 0 || tfList[0] == nil { return nil } - params := make(map[string][]*string) + apiObject := &awstypes.MaintenanceWindowLambdaParameters{} + tfMap := tfList[0].(map[string]interface{}) - for _, v := range config { - paramConfig := v.(map[string]interface{}) - params[paramConfig[names.AttrName].(string)] = flex.ExpandStringList(paramConfig[names.AttrValues].([]interface{})) + if v, ok := tfMap["client_context"]; ok && len(v.(string)) != 0 { + apiObject.ClientContext = aws.String(v.(string)) + } + if v, ok := tfMap["payload"]; ok && len(v.(string)) != 0 { + apiObject.Payload = []byte(v.(string)) + } + if v, ok := tfMap["qualifier"]; ok && len(v.(string)) != 0 { + apiObject.Qualifier = aws.String(v.(string)) } - return params + return apiObject } -func flattenTaskInvocationCommonParameters(parameters map[string][]*string) []interface{} { - attributes := make([]interface{}, 0, len(parameters)) +func flattenTaskInvocationLambdaParameters(apiObject *awstypes.MaintenanceWindowLambdaParameters) []interface{} { + tfMap := make(map[string]interface{}) - keys := make([]string, 0, len(parameters)) - for k := range parameters { - keys = append(keys, k) + if apiObject.ClientContext != nil { + tfMap["client_context"] = aws.ToString(apiObject.ClientContext) } - sort.Strings(keys) - - for _, key := range keys { - values := make([]string, 0) - for _, value := range parameters[key] { - values = append(values, aws.StringValue(value)) - } - params := map[string]interface{}{ - names.AttrName: key, - names.AttrValues: values, - } - attributes = append(attributes, params) + if apiObject.Payload != nil { + tfMap["payload"] = string(apiObject.Payload) + } + if apiObject.Qualifier != nil { + tfMap["qualifier"] = aws.ToString(apiObject.Qualifier) } - return attributes + return []interface{}{tfMap} } -func resourceMaintenanceWindowTaskCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) +func expandTaskInvocationRunCommandParameters(tfList []interface{}) *awstypes.MaintenanceWindowRunCommandParameters { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } - log.Printf("[INFO] Registering SSM Maintenance Window Task") + apiObject := &awstypes.MaintenanceWindowRunCommandParameters{} + tfMap := tfList[0].(map[string]interface{}) - params := &ssm.RegisterTaskWithMaintenanceWindowInput{ - WindowId: aws.String(d.Get("window_id").(string)), - TaskType: aws.String(d.Get("task_type").(string)), - TaskArn: aws.String(d.Get("task_arn").(string)), + if v, ok := tfMap["cloudwatch_config"]; ok && len(v.([]interface{})) > 0 { + apiObject.CloudWatchOutputConfig = expandTaskInvocationRunCommandParametersCloudWatchConfig(v.([]interface{})) } - - if v, ok := d.GetOk("max_errors"); ok { - params.MaxErrors = aws.String(v.(string)) + if v, ok := tfMap["comment"]; ok && len(v.(string)) != 0 { + apiObject.Comment = aws.String(v.(string)) } - - if v, ok := d.GetOk("max_concurrency"); ok { - params.MaxConcurrency = aws.String(v.(string)) + if v, ok := tfMap["document_hash"]; ok && len(v.(string)) != 0 { + apiObject.DocumentHash = aws.String(v.(string)) } - - if v, ok := d.GetOk("cutoff_behavior"); ok { - params.CutoffBehavior = aws.String(v.(string)) + if v, ok := tfMap["document_hash_type"]; ok && len(v.(string)) != 0 { + apiObject.DocumentHashType = awstypes.DocumentHashType(v.(string)) } - - if v, ok := d.GetOk("targets"); ok { - params.Targets = expandTargets(v.([]interface{})) + if v, ok := tfMap["document_version"]; ok && len(v.(string)) != 0 { + apiObject.DocumentVersion = aws.String(v.(string)) } - - if v, ok := d.GetOk("service_role_arn"); ok { - params.ServiceRoleArn = aws.String(v.(string)) + if v, ok := tfMap["notification_config"]; ok && len(v.([]interface{})) > 0 { + apiObject.NotificationConfig = expandTaskInvocationRunCommandParametersNotificationConfig(v.([]interface{})) } - - if v, ok := d.GetOk(names.AttrName); ok { - params.Name = aws.String(v.(string)) + if v, ok := tfMap["output_s3_bucket"]; ok && len(v.(string)) != 0 { + apiObject.OutputS3BucketName = aws.String(v.(string)) } - - if v, ok := d.GetOk(names.AttrDescription); ok { - params.Description = aws.String(v.(string)) + if v, ok := tfMap["output_s3_key_prefix"]; ok && len(v.(string)) != 0 { + apiObject.OutputS3KeyPrefix = aws.String(v.(string)) } - - if v, ok := d.GetOk(names.AttrPriority); ok { - params.Priority = aws.Int64(int64(v.(int))) + if v, ok := tfMap[names.AttrParameter]; ok && len(v.(*schema.Set).List()) > 0 { + apiObject.Parameters = expandTaskInvocationCommonParameters(v.(*schema.Set).List()) } - - if v, ok := d.GetOk("task_invocation_parameters"); ok { - params.TaskInvocationParameters = expandTaskInvocationParameters(v.([]interface{})) + if v, ok := tfMap["service_role_arn"]; ok && len(v.(string)) != 0 { + apiObject.ServiceRoleArn = aws.String(v.(string)) } - - resp, err := conn.RegisterTaskWithMaintenanceWindowWithContext(ctx, params) - if err != nil { - return sdkdiag.AppendErrorf(diags, "creating SSM Maintenance Window Task: %s", err) + if v, ok := tfMap["timeout_seconds"]; ok && v.(int) != 0 { + apiObject.TimeoutSeconds = aws.Int32(int32(v.(int))) } - d.SetId(aws.StringValue(resp.WindowTaskId)) - - return append(diags, resourceMaintenanceWindowTaskRead(ctx, d, meta)...) + return apiObject } -func resourceMaintenanceWindowTaskRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) - windowID := d.Get("window_id").(string) +func flattenTaskInvocationRunCommandParameters(apiObject *awstypes.MaintenanceWindowRunCommandParameters) []interface{} { + tfMap := make(map[string]interface{}) - params := &ssm.GetMaintenanceWindowTaskInput{ - WindowId: aws.String(windowID), - WindowTaskId: aws.String(d.Id()), + if apiObject.CloudWatchOutputConfig != nil { + tfMap["cloudwatch_config"] = flattenTaskInvocationRunCommandParametersCloudWatchConfig(apiObject.CloudWatchOutputConfig) } - resp, err := conn.GetMaintenanceWindowTaskWithContext(ctx, params) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, ssm.ErrCodeDoesNotExistException) { - log.Printf("[WARN] Maintenance Window (%s) Task (%s) not found, removing from state", windowID, d.Id()) - d.SetId("") - return diags + if apiObject.Comment != nil { + tfMap["comment"] = aws.ToString(apiObject.Comment) } - if err != nil { - return sdkdiag.AppendErrorf(diags, "getting Maintenance Window (%s) Task (%s): %s", windowID, d.Id(), err) + if apiObject.DocumentHash != nil { + tfMap["document_hash"] = aws.ToString(apiObject.DocumentHash) } - - windowTaskID := aws.StringValue(resp.WindowTaskId) - d.Set("window_id", resp.WindowId) - d.Set("window_task_id", windowTaskID) - d.Set("max_concurrency", resp.MaxConcurrency) - d.Set("max_errors", resp.MaxErrors) - d.Set("task_type", resp.TaskType) - d.Set("service_role_arn", resp.ServiceRoleArn) - d.Set("task_arn", resp.TaskArn) - d.Set(names.AttrPriority, resp.Priority) - d.Set(names.AttrName, resp.Name) - d.Set(names.AttrDescription, resp.Description) - d.Set("cutoff_behavior", resp.CutoffBehavior) - - if resp.TaskInvocationParameters != nil { - if err := d.Set("task_invocation_parameters", flattenTaskInvocationParameters(resp.TaskInvocationParameters)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting task_invocation_parameters error: %#v", err) - } + tfMap["document_hash_type"] = apiObject.DocumentHashType + if apiObject.DocumentVersion != nil { + tfMap["document_version"] = aws.ToString(apiObject.DocumentVersion) + } + if apiObject.NotificationConfig != nil { + tfMap["notification_config"] = flattenTaskInvocationRunCommandParametersNotificationConfig(apiObject.NotificationConfig) } + if apiObject.OutputS3BucketName != nil { + tfMap["output_s3_bucket"] = aws.ToString(apiObject.OutputS3BucketName) + } + if apiObject.OutputS3KeyPrefix != nil { + tfMap["output_s3_key_prefix"] = aws.ToString(apiObject.OutputS3KeyPrefix) + } + if apiObject.Parameters != nil { + tfMap[names.AttrParameter] = flattenTaskInvocationCommonParameters(apiObject.Parameters) + } + if apiObject.ServiceRoleArn != nil { + tfMap["service_role_arn"] = aws.ToString(apiObject.ServiceRoleArn) + } + if apiObject.TimeoutSeconds != nil { + tfMap["timeout_seconds"] = aws.ToInt32(apiObject.TimeoutSeconds) + } + + return []interface{}{tfMap} +} - if err := d.Set("targets", flattenTargets(resp.Targets)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting targets error: %#v", err) +func expandTaskInvocationStepFunctionsParameters(tfList []interface{}) *awstypes.MaintenanceWindowStepFunctionsParameters { + if len(tfList) == 0 || tfList[0] == nil { + return nil } - arn := arn.ARN{ - Partition: meta.(*conns.AWSClient).Partition, - Service: "ssm", - Region: meta.(*conns.AWSClient).Region, - AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("windowtask/%s", windowTaskID), - }.String() - d.Set(names.AttrARN, arn) + apiObject := &awstypes.MaintenanceWindowStepFunctionsParameters{} + tfMap := tfList[0].(map[string]interface{}) - return diags + if v, ok := tfMap["input"]; ok && len(v.(string)) != 0 { + apiObject.Input = aws.String(v.(string)) + } + if v, ok := tfMap[names.AttrName]; ok && len(v.(string)) != 0 { + apiObject.Name = aws.String(v.(string)) + } + + return apiObject } -func resourceMaintenanceWindowTaskUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) - windowID := d.Get("window_id").(string) +func flattenTaskInvocationStepFunctionsParameters(apiObject *awstypes.MaintenanceWindowStepFunctionsParameters) []interface{} { + tfMap := make(map[string]interface{}) - params := &ssm.UpdateMaintenanceWindowTaskInput{ - Priority: aws.Int64(int64(d.Get(names.AttrPriority).(int))), - WindowId: aws.String(windowID), - WindowTaskId: aws.String(d.Id()), - TaskArn: aws.String(d.Get("task_arn").(string)), - Replace: aws.Bool(true), + if apiObject.Input != nil { + tfMap["input"] = aws.ToString(apiObject.Input) } - - if v, ok := d.GetOk("service_role_arn"); ok { - params.ServiceRoleArn = aws.String(v.(string)) + if apiObject.Name != nil { + tfMap[names.AttrName] = aws.ToString(apiObject.Name) } - if v, ok := d.GetOk("max_errors"); ok { - params.MaxErrors = aws.String(v.(string)) - } + return []interface{}{tfMap} +} - if v, ok := d.GetOk("max_concurrency"); ok { - params.MaxConcurrency = aws.String(v.(string)) +func expandTaskInvocationRunCommandParametersNotificationConfig(tfList []interface{}) *awstypes.NotificationConfig { + if len(tfList) == 0 || tfList[0] == nil { + return nil } - if v, ok := d.GetOk("targets"); ok { - params.Targets = expandTargets(v.([]interface{})) - } else { - params.MaxConcurrency = nil - params.MaxErrors = nil - } + apiObject := &awstypes.NotificationConfig{} + tfMap := tfList[0].(map[string]interface{}) - if v, ok := d.GetOk("cutoff_behavior"); ok { - params.CutoffBehavior = aws.String(v.(string)) + if v, ok := tfMap["notification_arn"]; ok && len(v.(string)) != 0 { + apiObject.NotificationArn = aws.String(v.(string)) } - - if v, ok := d.GetOk(names.AttrName); ok { - params.Name = aws.String(v.(string)) + if v, ok := tfMap["notification_events"]; ok && len(v.([]interface{})) > 0 { + apiObject.NotificationEvents = flex.ExpandStringyValueList[awstypes.NotificationEvent](v.([]interface{})) + } + if v, ok := tfMap["notification_type"]; ok && len(v.(string)) != 0 { + apiObject.NotificationType = awstypes.NotificationType(v.(string)) } - if v, ok := d.GetOk(names.AttrDescription); ok { - params.Description = aws.String(v.(string)) + return apiObject +} + +func flattenTaskInvocationRunCommandParametersNotificationConfig(apiObject *awstypes.NotificationConfig) []interface{} { + tfMap := make(map[string]interface{}) + + if apiObject.NotificationArn != nil { + tfMap["notification_arn"] = aws.ToString(apiObject.NotificationArn) + } + if apiObject.NotificationEvents != nil { + tfMap["notification_events"] = apiObject.NotificationEvents } + tfMap["notification_type"] = apiObject.NotificationType - if v, ok := d.GetOk("task_invocation_parameters"); ok { - params.TaskInvocationParameters = expandTaskInvocationParameters(v.([]interface{})) + return []interface{}{tfMap} +} + +func expandTaskInvocationRunCommandParametersCloudWatchConfig(tfList []interface{}) *awstypes.CloudWatchOutputConfig { + if len(tfList) == 0 || tfList[0] == nil { + return nil } - _, err := conn.UpdateMaintenanceWindowTaskWithContext(ctx, params) - if err != nil { - return sdkdiag.AppendErrorf(diags, "updating Maintenance Window (%s) Task (%s): %s", windowID, d.Id(), err) + apiObject := &awstypes.CloudWatchOutputConfig{} + tfMap := tfList[0].(map[string]interface{}) + + if v, ok := tfMap["cloudwatch_log_group_name"]; ok && len(v.(string)) != 0 { + apiObject.CloudWatchLogGroupName = aws.String(v.(string)) + } + if v, ok := tfMap["cloudwatch_output_enabled"]; ok { + apiObject.CloudWatchOutputEnabled = v.(bool) } - return append(diags, resourceMaintenanceWindowTaskRead(ctx, d, meta)...) + return apiObject } -func resourceMaintenanceWindowTaskDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) - - log.Printf("[INFO] Deregistering SSM Maintenance Window Task: %s", d.Id()) +func flattenTaskInvocationRunCommandParametersCloudWatchConfig(apiObject *awstypes.CloudWatchOutputConfig) []interface{} { + tfMap := make(map[string]interface{}) - params := &ssm.DeregisterTaskFromMaintenanceWindowInput{ - WindowId: aws.String(d.Get("window_id").(string)), - WindowTaskId: aws.String(d.Id()), + if apiObject.CloudWatchLogGroupName != nil { + tfMap["cloudwatch_log_group_name"] = aws.ToString(apiObject.CloudWatchLogGroupName) } + tfMap["cloudwatch_output_enabled"] = apiObject.CloudWatchOutputEnabled - _, err := conn.DeregisterTaskFromMaintenanceWindowWithContext(ctx, params) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeDoesNotExistException) { - return diags + return []interface{}{tfMap} +} + +func expandTaskInvocationCommonParameters(tfList []interface{}) map[string][]string { + if len(tfList) == 0 || tfList[0] == nil { + return nil } - if err != nil { - return sdkdiag.AppendErrorf(diags, "deregistering SSM Maintenance Window Task (%s): %s", d.Id(), err) + + apiObject := make(map[string][]string) + + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) + apiObject[tfMap[names.AttrName].(string)] = flex.ExpandStringValueList(tfMap[names.AttrValues].([]interface{})) } - return diags + return apiObject } -func resourceMaintenanceWindowTaskImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - idParts := strings.SplitN(d.Id(), "/", 2) - if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { - return nil, fmt.Errorf("unexpected format of ID (%q), expected /", d.Id()) - } +func flattenTaskInvocationCommonParameters(apiObject map[string][]string) []interface{} { + tfList := make([]interface{}, 0, len(apiObject)) - windowID := idParts[0] - windowTaskID := idParts[1] + keys := tfmaps.Keys(apiObject) + slices.Sort(keys) - d.Set("window_id", windowID) - d.SetId(windowTaskID) + for _, key := range keys { + tfList = append(tfList, map[string]interface{}{ + names.AttrName: key, + names.AttrValues: apiObject[key], + }) + } - return []*schema.ResourceData{d}, nil + return tfList } diff --git a/internal/service/ssm/maintenance_window_task_test.go b/internal/service/ssm/maintenance_window_task_test.go index 2a408cb11f2..79113013cce 100644 --- a/internal/service/ssm/maintenance_window_task_test.go +++ b/internal/service/ssm/maintenance_window_task_test.go @@ -9,21 +9,21 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "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/ssm" 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" tfssm "github.com/hashicorp/terraform-provider-aws/internal/service/ssm" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccSSMMaintenanceWindowTask_basic(t *testing.T) { ctx := acctest.Context(t) - var before, after ssm.MaintenanceWindowTask + var before, after ssm.GetMaintenanceWindowTaskOutput resourceName := "aws_ssm_maintenance_window_task.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -69,7 +69,7 @@ func TestAccSSMMaintenanceWindowTask_basic(t *testing.T) { func TestAccSSMMaintenanceWindowTask_noTarget(t *testing.T) { ctx := acctest.Context(t) - var before ssm.MaintenanceWindowTask + var before ssm.GetMaintenanceWindowTaskOutput resourceName := "aws_ssm_maintenance_window_task.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -98,7 +98,7 @@ func TestAccSSMMaintenanceWindowTask_noTarget(t *testing.T) { func TestAccSSMMaintenanceWindowTask_cutoff(t *testing.T) { ctx := acctest.Context(t) - var before ssm.MaintenanceWindowTask + var before ssm.GetMaintenanceWindowTaskOutput resourceName := "aws_ssm_maintenance_window_task.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -134,7 +134,7 @@ func TestAccSSMMaintenanceWindowTask_cutoff(t *testing.T) { func TestAccSSMMaintenanceWindowTask_noRole(t *testing.T) { ctx := acctest.Context(t) - var task ssm.MaintenanceWindowTask + var task ssm.GetMaintenanceWindowTaskOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_maintenance_window_task.test" @@ -156,7 +156,7 @@ func TestAccSSMMaintenanceWindowTask_noRole(t *testing.T) { func TestAccSSMMaintenanceWindowTask_updateForcesNewResource(t *testing.T) { ctx := acctest.Context(t) - var before, after ssm.MaintenanceWindowTask + var before, after ssm.GetMaintenanceWindowTaskOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_maintenance_window_task.test" @@ -193,7 +193,7 @@ func TestAccSSMMaintenanceWindowTask_updateForcesNewResource(t *testing.T) { func TestAccSSMMaintenanceWindowTask_description(t *testing.T) { ctx := acctest.Context(t) - var task1, task2 ssm.MaintenanceWindowTask + var task1, task2 ssm.GetMaintenanceWindowTaskOutput resourceName := "aws_ssm_maintenance_window_task.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -230,7 +230,7 @@ func TestAccSSMMaintenanceWindowTask_description(t *testing.T) { func TestAccSSMMaintenanceWindowTask_taskInvocationAutomationParameters(t *testing.T) { ctx := acctest.Context(t) - var task ssm.MaintenanceWindowTask + var task ssm.GetMaintenanceWindowTaskOutput resourceName := "aws_ssm_maintenance_window_task.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -266,7 +266,7 @@ func TestAccSSMMaintenanceWindowTask_taskInvocationAutomationParameters(t *testi func TestAccSSMMaintenanceWindowTask_taskInvocationLambdaParameters(t *testing.T) { ctx := acctest.Context(t) - var task ssm.MaintenanceWindowTask + var task ssm.GetMaintenanceWindowTaskOutput resourceName := "aws_ssm_maintenance_window_task.test" rString := sdkacctest.RandString(8) rInt := sdkacctest.RandInt() @@ -300,7 +300,7 @@ func TestAccSSMMaintenanceWindowTask_taskInvocationLambdaParameters(t *testing.T func TestAccSSMMaintenanceWindowTask_taskInvocationRunCommandParameters(t *testing.T) { ctx := acctest.Context(t) - var task ssm.MaintenanceWindowTask + var task ssm.GetMaintenanceWindowTaskOutput resourceName := "aws_ssm_maintenance_window_task.test" serviceRoleResourceName := "aws_iam_role.test" s3BucketResourceName := "aws_s3_bucket.test" @@ -343,7 +343,7 @@ func TestAccSSMMaintenanceWindowTask_taskInvocationRunCommandParameters(t *testi func TestAccSSMMaintenanceWindowTask_taskInvocationRunCommandParametersCloudWatch(t *testing.T) { ctx := acctest.Context(t) - var task ssm.MaintenanceWindowTask + var task ssm.GetMaintenanceWindowTaskOutput resourceName := "aws_ssm_maintenance_window_task.test" serviceRoleResourceName := "aws_iam_role.test" cwResourceName := "aws_cloudwatch_log_group.test" @@ -396,7 +396,7 @@ func TestAccSSMMaintenanceWindowTask_taskInvocationRunCommandParametersCloudWatc func TestAccSSMMaintenanceWindowTask_taskInvocationStepFunctionParameters(t *testing.T) { ctx := acctest.Context(t) - var task ssm.MaintenanceWindowTask + var task ssm.GetMaintenanceWindowTaskOutput resourceName := "aws_ssm_maintenance_window_task.test" rString := sdkacctest.RandString(8) @@ -424,7 +424,7 @@ func TestAccSSMMaintenanceWindowTask_taskInvocationStepFunctionParameters(t *tes func TestAccSSMMaintenanceWindowTask_emptyNotification(t *testing.T) { ctx := acctest.Context(t) - var task ssm.MaintenanceWindowTask + var task ssm.GetMaintenanceWindowTaskOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_maintenance_window_task.test" @@ -447,7 +447,7 @@ func TestAccSSMMaintenanceWindowTask_emptyNotification(t *testing.T) { func TestAccSSMMaintenanceWindowTask_disappears(t *testing.T) { ctx := acctest.Context(t) - var before ssm.MaintenanceWindowTask + var before ssm.GetMaintenanceWindowTaskOutput resourceName := "aws_ssm_maintenance_window_task.test" name := sdkacctest.RandString(10) @@ -469,18 +469,16 @@ func TestAccSSMMaintenanceWindowTask_disappears(t *testing.T) { }) } -func testAccCheckWindowsTaskNotRecreated(t *testing.T, - before, after *ssm.MaintenanceWindowTask) resource.TestCheckFunc { +func testAccCheckWindowsTaskNotRecreated(t *testing.T, before, after *ssm.GetMaintenanceWindowTaskOutput) resource.TestCheckFunc { return func(s *terraform.State) error { - if aws.StringValue(before.WindowTaskId) != aws.StringValue(after.WindowTaskId) { - t.Fatalf("Unexpected change of Windows Task IDs, but both were %s and %s", aws.StringValue(before.WindowTaskId), aws.StringValue(after.WindowTaskId)) + if aws.ToString(before.WindowTaskId) != aws.ToString(after.WindowTaskId) { + t.Fatalf("Unexpected change of Windows Task IDs, but both were %s and %s", aws.ToString(before.WindowTaskId), aws.ToString(after.WindowTaskId)) } return nil } } -func testAccCheckWindowsTaskRecreated(t *testing.T, - before, after *ssm.MaintenanceWindowTask) resource.TestCheckFunc { +func testAccCheckWindowsTaskRecreated(t *testing.T, before, after *ssm.GetMaintenanceWindowTaskOutput) resource.TestCheckFunc { return func(s *terraform.State) error { if before.WindowTaskId == after.WindowTaskId { t.Fatalf("Expected change of Windows Task IDs, but both were %v", before.WindowTaskId) @@ -489,63 +487,47 @@ func testAccCheckWindowsTaskRecreated(t *testing.T, } } -func testAccCheckMaintenanceWindowTaskExists(ctx context.Context, n string, task *ssm.MaintenanceWindowTask) resource.TestCheckFunc { +func testAccCheckMaintenanceWindowTaskExists(ctx context.Context, n string, v *ssm.GetMaintenanceWindowTaskOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No SSM Maintenance Window Task Window ID is set") - } + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + output, err := tfssm.FindMaintenanceWindowTaskByTwoPartKey(ctx, conn, rs.Primary.Attributes["window_id"], rs.Primary.ID) - resp, err := conn.DescribeMaintenanceWindowTasksWithContext(ctx, &ssm.DescribeMaintenanceWindowTasksInput{ - WindowId: aws.String(rs.Primary.Attributes["window_id"]), - }) if err != nil { return err } - for _, i := range resp.Tasks { - if aws.StringValue(i.WindowTaskId) == rs.Primary.ID { - *task = *i - return nil - } - } + *v = *output - return fmt.Errorf("No AWS SSM Maintenance window task found") + return nil } } func testAccCheckMaintenanceWindowTaskDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ssm_maintenance_window_task" { continue } - out, err := conn.DescribeMaintenanceWindowTasksWithContext(ctx, &ssm.DescribeMaintenanceWindowTasksInput{ - WindowId: aws.String(rs.Primary.Attributes["window_id"]), - }) + _, err := tfssm.FindMaintenanceWindowTaskByTwoPartKey(ctx, conn, rs.Primary.Attributes["window_id"], rs.Primary.ID) - if err != nil { - // Verify the error is what we want - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeDoesNotExistException) { - continue - } - return err + if tfresource.NotFound(err) { + continue } - if len(out.Tasks) > 0 { - return fmt.Errorf("Expected AWS SSM Maintenance Task to be gone, but was still found") + if err != nil { + return err } - return nil + return fmt.Errorf("SSM Maintenance Window Task %s still exists", rs.Primary.ID) } return nil diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index e64ca585b6c..4ec774efd6c 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -95,8 +95,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Maintenance Window Target", }, { - Factory: ResourceMaintenanceWindowTask, + Factory: resourceMaintenanceWindowTask, TypeName: "aws_ssm_maintenance_window_task", + Name: "Maintenance Window Task", }, { Factory: ResourceParameter, From fcf551c84105f84c19ced8996a746fcb8305b41d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 11:22:24 -0400 Subject: [PATCH 12/34] r/aws_ssm_maintenance_window: Migrate to AWS SDK for Go v2. --- internal/service/ssm/exports_test.go | 2 + internal/service/ssm/maintenance_window.go | 49 ++++++++++--------- .../service/ssm/maintenance_window_test.go | 10 ++-- internal/service/ssm/service_package_gen.go | 2 +- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index fa67169f463..f592c156ed6 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -9,6 +9,7 @@ var ( ResourceAssociation = resourceAssociation ResourceDefaultPatchBaseline = resourceDefaultPatchBaseline ResourceDocument = resourceDocument + ResourceMaintenanceWindow = resourceMaintenanceWindow ResourceMaintenanceWindowTarget = resourceMaintenanceWindowTarget ResourceMaintenanceWindowTask = resourceMaintenanceWindowTask ResourcePatchBaseline = resourcePatchBaseline @@ -16,6 +17,7 @@ var ( FindActivationByID = findActivationByID FindAssociationByID = findAssociationByID FindDocumentByName = findDocumentByName + FindMaintenanceWindowByID = findMaintenanceWindowByID FindMaintenanceWindowTargetByID = findMaintenanceWindowTargetByID FindMaintenanceWindowTaskByTwoPartKey = findMaintenanceWindowTaskByTwoPartKey FindPatchBaselineByID = findPatchBaselineByID diff --git a/internal/service/ssm/maintenance_window.go b/internal/service/ssm/maintenance_window.go index 687304c3949..ade63c940e9 100644 --- a/internal/service/ssm/maintenance_window.go +++ b/internal/service/ssm/maintenance_window.go @@ -7,14 +7,15 @@ import ( "context" "log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "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/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/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" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -24,7 +25,7 @@ import ( // @SDKResource("aws_ssm_maintenance_window", name="Maintenance Window") // @Tags(identifierAttribute="id", resourceType="MaintenanceWindow") -func ResourceMaintenanceWindow() *schema.Resource { +func resourceMaintenanceWindow() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceMaintenanceWindowCreate, ReadWithoutTimeout: resourceMaintenanceWindowRead, @@ -93,13 +94,13 @@ func ResourceMaintenanceWindow() *schema.Resource { func resourceMaintenanceWindowCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) name := d.Get(names.AttrName).(string) input := &ssm.CreateMaintenanceWindowInput{ - AllowUnassociatedTargets: aws.Bool(d.Get("allow_unassociated_targets").(bool)), - Cutoff: aws.Int64(int64(d.Get("cutoff").(int))), - Duration: aws.Int64(int64(d.Get(names.AttrDuration).(int))), + AllowUnassociatedTargets: d.Get("allow_unassociated_targets").(bool), + Cutoff: int32(d.Get("cutoff").(int)), + Duration: aws.Int32(int32(d.Get(names.AttrDuration).(int))), Name: aws.String(name), Schedule: aws.String(d.Get(names.AttrSchedule).(string)), Tags: getTagsIn(ctx), @@ -114,7 +115,7 @@ func resourceMaintenanceWindowCreate(ctx context.Context, d *schema.ResourceData } if v, ok := d.GetOk("schedule_offset"); ok { - input.ScheduleOffset = aws.Int64(int64(v.(int))) + input.ScheduleOffset = aws.Int32(int32(v.(int))) } if v, ok := d.GetOk("schedule_timezone"); ok { @@ -125,13 +126,13 @@ func resourceMaintenanceWindowCreate(ctx context.Context, d *schema.ResourceData input.StartDate = aws.String(v.(string)) } - output, err := conn.CreateMaintenanceWindowWithContext(ctx, input) + output, err := conn.CreateMaintenanceWindow(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "creating SSM Maintenance Window (%s): %s", name, err) } - d.SetId(aws.StringValue(output.WindowId)) + d.SetId(aws.ToString(output.WindowId)) if !d.Get(names.AttrEnabled).(bool) { input := &ssm.UpdateMaintenanceWindowInput{ @@ -139,7 +140,7 @@ func resourceMaintenanceWindowCreate(ctx context.Context, d *schema.ResourceData WindowId: aws.String(d.Id()), } - _, err := conn.UpdateMaintenanceWindowWithContext(ctx, input) + _, err := conn.UpdateMaintenanceWindow(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "disabling SSM Maintenance Window (%s): %s", d.Id(), err) @@ -151,9 +152,9 @@ func resourceMaintenanceWindowCreate(ctx context.Context, d *schema.ResourceData func resourceMaintenanceWindowRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - output, err := FindMaintenanceWindowByID(ctx, conn, d.Id()) + output, err := findMaintenanceWindowByID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] SSM Maintenance Window %s not found, removing from state", d.Id()) @@ -182,15 +183,15 @@ func resourceMaintenanceWindowRead(ctx context.Context, d *schema.ResourceData, func resourceMaintenanceWindowUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) if d.HasChangesExcept(names.AttrTags, names.AttrTagsAll) { // Replace must be set otherwise its not possible to remove optional attributes, e.g. // ValidationException: 1 validation error detected: Value '' at 'startDate' failed to satisfy constraint: Member must have length greater than or equal to 1 input := &ssm.UpdateMaintenanceWindowInput{ AllowUnassociatedTargets: aws.Bool(d.Get("allow_unassociated_targets").(bool)), - Cutoff: aws.Int64(int64(d.Get("cutoff").(int))), - Duration: aws.Int64(int64(d.Get(names.AttrDuration).(int))), + Cutoff: aws.Int32(int32(d.Get("cutoff").(int))), + Duration: aws.Int32(int32(d.Get(names.AttrDuration).(int))), Enabled: aws.Bool(d.Get(names.AttrEnabled).(bool)), Name: aws.String(d.Get(names.AttrName).(string)), Replace: aws.Bool(true), @@ -207,7 +208,7 @@ func resourceMaintenanceWindowUpdate(ctx context.Context, d *schema.ResourceData } if v, ok := d.GetOk("schedule_offset"); ok { - input.ScheduleOffset = aws.Int64(int64(v.(int))) + input.ScheduleOffset = aws.Int32(int32(v.(int))) } if v, ok := d.GetOk("schedule_timezone"); ok { @@ -218,7 +219,7 @@ func resourceMaintenanceWindowUpdate(ctx context.Context, d *schema.ResourceData input.StartDate = aws.String(v.(string)) } - _, err := conn.UpdateMaintenanceWindowWithContext(ctx, input) + _, err := conn.UpdateMaintenanceWindow(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "updating SSM Maintenance Window (%s): %s", d.Id(), err) @@ -230,10 +231,10 @@ func resourceMaintenanceWindowUpdate(ctx context.Context, d *schema.ResourceData func resourceMaintenanceWindowDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) log.Printf("[INFO] Deleting SSM Maintenance Window: %s", d.Id()) - _, err := conn.DeleteMaintenanceWindowWithContext(ctx, &ssm.DeleteMaintenanceWindowInput{ + _, err := conn.DeleteMaintenanceWindow(ctx, &ssm.DeleteMaintenanceWindowInput{ WindowId: aws.String(d.Id()), }) @@ -244,14 +245,14 @@ func resourceMaintenanceWindowDelete(ctx context.Context, d *schema.ResourceData return diags } -func FindMaintenanceWindowByID(ctx context.Context, conn *ssm.SSM, id string) (*ssm.GetMaintenanceWindowOutput, error) { +func findMaintenanceWindowByID(ctx context.Context, conn *ssm.Client, id string) (*ssm.GetMaintenanceWindowOutput, error) { input := &ssm.GetMaintenanceWindowInput{ WindowId: aws.String(id), } - output, err := conn.GetMaintenanceWindowWithContext(ctx, input) + output, err := conn.GetMaintenanceWindow(ctx, input) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeDoesNotExistException) { + if errs.IsA[*awstypes.DoesNotExistException](err) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, diff --git a/internal/service/ssm/maintenance_window_test.go b/internal/service/ssm/maintenance_window_test.go index 6827efbb70c..5caaf0d3efb 100644 --- a/internal/service/ssm/maintenance_window_test.go +++ b/internal/service/ssm/maintenance_window_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go-v2/service/ssm" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -515,11 +515,7 @@ func testAccCheckMaintenanceWindowExists(ctx context.Context, n string, v *ssm.G return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No SSM Maintenance Window ID is set") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) output, err := tfssm.FindMaintenanceWindowByID(ctx, conn, rs.Primary.ID) @@ -535,7 +531,7 @@ func testAccCheckMaintenanceWindowExists(ctx context.Context, n string, v *ssm.G func testAccCheckMaintenanceWindowDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ssm_maintenance_window" { diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index 4ec774efd6c..fa5a99be549 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -81,7 +81,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourceMaintenanceWindow, + Factory: resourceMaintenanceWindow, TypeName: "aws_ssm_maintenance_window", Name: "Maintenance Window", Tags: &types.ServicePackageResourceTags{ From 43a2e768b85c73effa03fe77d430535015af6405 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 11:27:04 -0400 Subject: [PATCH 13/34] d/aws_ssm_maintenance_windows: Migrate to AWS SDK for Go v2. --- .../ssm/maintenance_windows_data_source.go | 60 ++++++++----------- internal/service/ssm/service_package_gen.go | 3 +- 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/internal/service/ssm/maintenance_windows_data_source.go b/internal/service/ssm/maintenance_windows_data_source.go index cac81b7d63f..7afa8ec8c66 100644 --- a/internal/service/ssm/maintenance_windows_data_source.go +++ b/internal/service/ssm/maintenance_windows_data_source.go @@ -6,20 +6,23 @@ package ssm import ( "context" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "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" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_ssm_maintenance_windows") -func DataSourceMaintenanceWindows() *schema.Resource { +// @SDKDataSource("aws_ssm_maintenance_windows", name="Maintenance Windows") +func dataSourceMaintenanceWindows() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataMaintenanceWindowsRead, + Schema: map[string]*schema.Schema{ names.AttrFilter: { Type: schema.TypeSet, @@ -30,7 +33,6 @@ func DataSourceMaintenanceWindows() *schema.Resource { Type: schema.TypeString, Required: true, }, - names.AttrValues: { Type: schema.TypeList, Required: true, @@ -50,7 +52,7 @@ func DataSourceMaintenanceWindows() *schema.Resource { func dataMaintenanceWindowsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) input := &ssm.DescribeMaintenanceWindowsInput{} @@ -58,46 +60,32 @@ func dataMaintenanceWindowsRead(ctx context.Context, d *schema.ResourceData, met input.Filters = expandMaintenanceWindowFilters(v.(*schema.Set).List()) } - var results []*ssm.MaintenanceWindowIdentity - - err := conn.DescribeMaintenanceWindowsPagesWithContext(ctx, input, func(page *ssm.DescribeMaintenanceWindowsOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, windowIdentities := range page.WindowIdentities { - if windowIdentities == nil { - continue - } + var output []awstypes.MaintenanceWindowIdentity - results = append(results, windowIdentities) + pages := ssm.NewDescribeMaintenanceWindowsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading SSM Maintenance Windows: %s", err) } - return !lastPage - }) - - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading SSM Maintenance Windows: %s", err) - } - - var windowIDs []string - - for _, r := range results { - windowIDs = append(windowIDs, aws.StringValue(r.WindowId)) + output = append(output, page.WindowIdentities...) } d.SetId(meta.(*conns.AWSClient).Region) - d.Set("ids", windowIDs) + d.Set("ids", tfslices.ApplyToAll(output, func(v awstypes.MaintenanceWindowIdentity) string { + return aws.ToString(v.WindowId) + })) return diags } -func expandMaintenanceWindowFilters(tfList []interface{}) []*ssm.MaintenanceWindowFilter { +func expandMaintenanceWindowFilters(tfList []interface{}) []awstypes.MaintenanceWindowFilter { if len(tfList) == 0 { return nil } - var apiObjects []*ssm.MaintenanceWindowFilter + var apiObjects []awstypes.MaintenanceWindowFilter for _, tfMapRaw := range tfList { tfMap, ok := tfMapRaw.(map[string]interface{}) @@ -112,25 +100,25 @@ func expandMaintenanceWindowFilters(tfList []interface{}) []*ssm.MaintenanceWind continue } - apiObjects = append(apiObjects, apiObject) + apiObjects = append(apiObjects, *apiObject) } return apiObjects } -func expandMaintenanceWindowFilter(tfMap map[string]interface{}) *ssm.MaintenanceWindowFilter { +func expandMaintenanceWindowFilter(tfMap map[string]interface{}) *awstypes.MaintenanceWindowFilter { if tfMap == nil { return nil } - apiObject := &ssm.MaintenanceWindowFilter{} + apiObject := &awstypes.MaintenanceWindowFilter{} if v, ok := tfMap[names.AttrName].(string); ok && v != "" { apiObject.Key = aws.String(v) } if v, ok := tfMap[names.AttrValues].([]interface{}); ok && len(v) > 0 { - apiObject.Values = flex.ExpandStringList(v) + apiObject.Values = flex.ExpandStringValueList(v) } return apiObject diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index fa5a99be549..ba90ff00542 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -35,8 +35,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Name: "Instances", }, { - Factory: DataSourceMaintenanceWindows, + Factory: dataSourceMaintenanceWindows, TypeName: "aws_ssm_maintenance_windows", + Name: "Maintenance Windows", }, { Factory: DataSourceParameter, From d21628eb77e8e26f0e3d4e27c0f3166185b2aab2 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 12:07:18 -0400 Subject: [PATCH 14/34] r/aws_ssm_parameter: Migrate to AWS SDK for Go v2. --- internal/service/ssm/exports_test.go | 2 + internal/service/ssm/parameter.go | 285 +++++++++++--------- internal/service/ssm/parameter_test.go | 204 +++++--------- internal/service/ssm/service_package_gen.go | 2 +- 4 files changed, 241 insertions(+), 252 deletions(-) diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index f592c156ed6..342c971870a 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -12,6 +12,7 @@ var ( ResourceMaintenanceWindow = resourceMaintenanceWindow ResourceMaintenanceWindowTarget = resourceMaintenanceWindowTarget ResourceMaintenanceWindowTask = resourceMaintenanceWindowTask + ResourceParameter = resourceParameter ResourcePatchBaseline = resourcePatchBaseline FindActivationByID = findActivationByID @@ -20,5 +21,6 @@ var ( FindMaintenanceWindowByID = findMaintenanceWindowByID FindMaintenanceWindowTargetByID = findMaintenanceWindowTargetByID FindMaintenanceWindowTaskByTwoPartKey = findMaintenanceWindowTaskByTwoPartKey + FindParameterByName = findParameterByName FindPatchBaselineByID = findPatchBaselineByID ) diff --git a/internal/service/ssm/parameter.go b/internal/service/ssm/parameter.go index e63aea86d0b..e650a56c81a 100644 --- a/internal/service/ssm/parameter.go +++ b/internal/service/ssm/parameter.go @@ -5,19 +5,21 @@ package ssm import ( "context" - "fmt" "log" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "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/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/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/customdiff" "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/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -25,14 +27,9 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -const ( - // Maximum amount of time to wait for asynchronous validation on SSM Parameter creation. - parameterCreationValidationTimeout = 2 * time.Minute -) - // @SDKResource("aws_ssm_parameter", name="Parameter") // @Tags(identifierAttribute="id", resourceType="Parameter") -func ResourceParameter() *schema.Resource { +func resourceParameter() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceParameterCreate, ReadWithoutTimeout: resourceParameterRead, @@ -95,21 +92,21 @@ func ResourceParameter() *schema.Resource { names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), "tier": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice(ssm.ParameterTier_Values(), false), + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: enum.Validate[awstypes.ParameterTier](), DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { if old != "" { - return new == ssm.ParameterTierIntelligentTiering + return awstypes.ParameterTier(new) == awstypes.ParameterTierIntelligentTiering } return false }, }, names.AttrType: { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(ssm.ParameterType_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.ParameterType](), }, names.AttrValue: { Type: schema.TypeString, @@ -128,7 +125,7 @@ func ResourceParameter() *schema.Resource { // Prevent the following error during tier update from Advanced to Standard: // ValidationException: This parameter uses the advanced-parameter tier. You can't downgrade a parameter from the advanced-parameter tier to the standard-parameter tier. If necessary, you can delete the advanced parameter and recreate it as a standard parameter. customdiff.ForceNewIfChange("tier", func(_ context.Context, old, new, meta interface{}) bool { - return old.(string) == ssm.ParameterTierAdvanced && new.(string) == ssm.ParameterTierStandard + return awstypes.ParameterTier(old.(string)) == awstypes.ParameterTierAdvanced && awstypes.ParameterTier(new.(string)) == awstypes.ParameterTierStandard }), customdiff.ComputedIf(names.AttrVersion, func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) bool { return diff.HasChange(names.AttrValue) @@ -147,25 +144,20 @@ func ResourceParameter() *schema.Resource { func resourceParameterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) name := d.Get(names.AttrName).(string) - + typ := awstypes.ParameterType(d.Get(names.AttrType).(string)) value := d.Get(names.AttrValue).(string) if v, ok := d.Get("insecure_value").(string); ok && v != "" { value = v } - input := &ssm.PutParameterInput{ + AllowedPattern: aws.String(d.Get("allowed_pattern").(string)), Name: aws.String(name), - Type: aws.String(d.Get(names.AttrType).(string)), + Overwrite: aws.Bool(shouldUpdateParameter(d)), + Type: typ, Value: aws.String(value), - Overwrite: aws.Bool(ShouldUpdateParameter(d)), - AllowedPattern: aws.String(d.Get("allowed_pattern").(string)), - } - - if v, ok := d.GetOk("tier"); ok { - input.Tier = aws.String(v.(string)) } if v, ok := d.GetOk("data_type"); ok { @@ -176,24 +168,28 @@ func resourceParameterCreate(ctx context.Context, d *schema.ResourceData, meta i input.Description = aws.String(v.(string)) } - if keyID, ok := d.GetOk("key_id"); ok && d.Get(names.AttrType).(string) == ssm.ParameterTypeSecureString { - input.SetKeyId(keyID.(string)) + if v, ok := d.GetOk("key_id"); ok && typ == awstypes.ParameterTypeSecureString { + input.KeyId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("tier"); ok { + input.Tier = awstypes.ParameterTier(v.(string)) } // AWS SSM Service only supports PutParameter requests with Tags // iff Overwrite is not provided or is false; in this resource's case, // the Overwrite value is always set in the paramInput so we check for the value tags := getTagsIn(ctx) - if !aws.BoolValue(input.Overwrite) { + if !aws.ToBool(input.Overwrite) { input.Tags = tags } - _, err := conn.PutParameterWithContext(ctx, input) + _, err := conn.PutParameter(ctx, input) - if tfawserr.ErrMessageContains(err, "ValidationException", "Tier is not supported") { + if tfawserr.ErrMessageContains(err, errCodeValidationException, "Tier is not supported") { log.Printf("[WARN] Creating SSM Parameter (%s): tier %q not supported, using default", name, d.Get("tier").(string)) - input.Tier = nil - _, err = conn.PutParameterWithContext(ctx, input) + input.Tier = "" + _, err = conn.PutParameter(ctx, input) } if err != nil { @@ -204,7 +200,7 @@ func resourceParameterCreate(ctx context.Context, d *schema.ResourceData, meta i // Tags and Overwrite set to true, we make an additional API call // to Update the resource's tags if necessary if len(tags) > 0 && input.Tags == nil { - if err := createTags(ctx, conn, name, ssm.ResourceTypeForTaggingParameter, tags); err != nil { + if err := createTags(ctx, conn, name, string(awstypes.ResourceTypeForTaggingParameter), tags); err != nil { return sdkdiag.AppendErrorf(diags, "setting SSM Parameter (%s) tags: %s", name, err) } } @@ -216,35 +212,27 @@ func resourceParameterCreate(ctx context.Context, d *schema.ResourceData, meta i func resourceParameterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) - - input := &ssm.GetParameterInput{ - Name: aws.String(d.Id()), - WithDecryption: aws.Bool(true), - } - - var resp *ssm.GetParameterOutput - err := retry.RetryContext(ctx, parameterCreationValidationTimeout, func() *retry.RetryError { - var err error - resp, err = conn.GetParameterWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeParameterNotFound) && d.IsNewResource() && d.Get("data_type").(string) == "aws:ec2:image" { - return retry.RetryableError(fmt.Errorf("reading SSM Parameter (%s) after creation: this can indicate that the provided parameter value could not be validated by SSM", d.Id())) - } - - if err != nil { - return retry.NonRetryableError(err) - } - - return nil - }) + conn := meta.(*conns.AWSClient).SSMClient(ctx) + + const ( + // Maximum amount of time to wait for asynchronous validation on SSM Parameter creation. + timeout = 2 * time.Minute + ) + outputRaw, err := tfresource.RetryWhen(ctx, timeout, + func() (interface{}, error) { + return findParameterByName(ctx, conn, d.Id(), true) + }, + func(err error) (bool, error) { + if d.IsNewResource() && tfresource.NotFound(err) && d.Get("data_type").(string) == "aws:ec2:image" { + return true, err + } - if tfresource.TimedOut(err) { - resp, err = conn.GetParameterWithContext(ctx, input) - } + return false, err + }, + ) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeParameterNotFound) && !d.IsNewResource() { - log.Printf("[WARN] SSM Parameter (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSM Parameter %s not found, removing from state", d.Id()) d.SetId("") return diags } @@ -253,96 +241,85 @@ func resourceParameterRead(ctx context.Context, d *schema.ResourceData, meta int return sdkdiag.AppendErrorf(diags, "reading SSM Parameter (%s): %s", d.Id(), err) } - param := resp.Parameter + param := outputRaw.(*awstypes.Parameter) d.Set(names.AttrARN, param.ARN) - name := aws.StringValue(param.Name) - d.Set(names.AttrName, name) + d.Set(names.AttrName, param.Name) d.Set(names.AttrType, param.Type) d.Set(names.AttrVersion, param.Version) - if _, ok := d.GetOk("insecure_value"); ok && aws.StringValue(param.Type) != ssm.ParameterTypeSecureString { + if _, ok := d.GetOk("insecure_value"); ok && param.Type != awstypes.ParameterTypeSecureString { d.Set("insecure_value", param.Value) } else { d.Set(names.AttrValue, param.Value) } - if aws.StringValue(param.Type) == ssm.ParameterTypeSecureString && d.Get("insecure_value").(string) != "" { - return sdkdiag.AppendErrorf(diags, "invalid configuration, cannot set type = %s and insecure_value", aws.StringValue(param.Type)) + if param.Type == awstypes.ParameterTypeSecureString && d.Get("insecure_value").(string) != "" { + return sdkdiag.AppendErrorf(diags, "invalid configuration, cannot set type = %s and insecure_value", param.Type) } - describeParamsInput := &ssm.DescribeParametersInput{ - ParameterFilters: []*ssm.ParameterStringFilter{ - { - Key: aws.String("Name"), - Option: aws.String("Equals"), - Values: []*string{aws.String(name)}, - }, - }, - } - describeResp, err := conn.DescribeParametersWithContext(ctx, describeParamsInput) - if err != nil { - return sdkdiag.AppendErrorf(diags, "describing SSM parameter (%s): %s", d.Id(), err) - } + detail, err := findParameterMetadataByName(ctx, conn, d.Id()) - if !d.IsNewResource() && (describeResp == nil || len(describeResp.Parameters) == 0 || describeResp.Parameters[0] == nil) { - log.Printf("[WARN] SSM Parameter %q not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSM Parameter %s not found, removing from state", d.Id()) d.SetId("") return diags } - detail := describeResp.Parameters[0] - d.Set("key_id", detail.KeyId) - d.Set(names.AttrDescription, detail.Description) - d.Set("tier", detail.Tier) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading SSM Parameter metadata (%s): %s", d.Id(), err) + } + d.Set("allowed_pattern", detail.AllowedPattern) d.Set("data_type", detail.DataType) + d.Set(names.AttrDescription, detail.Description) + d.Set("key_id", detail.KeyId) + d.Set("tier", detail.Tier) return diags } func resourceParameterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) if d.HasChangesExcept("overwrite", names.AttrTags, names.AttrTagsAll) { + typ := awstypes.ParameterType(d.Get(names.AttrType).(string)) value := d.Get(names.AttrValue).(string) - if v, ok := d.Get("insecure_value").(string); ok && v != "" { value = v } - paramInput := &ssm.PutParameterInput{ - Name: aws.String(d.Get(names.AttrName).(string)), - Type: aws.String(d.Get(names.AttrType).(string)), - Tier: aws.String(d.Get("tier").(string)), - Value: aws.String(value), - Overwrite: aws.Bool(ShouldUpdateParameter(d)), + input := &ssm.PutParameterInput{ AllowedPattern: aws.String(d.Get("allowed_pattern").(string)), - } - - // Retrieve the value set in the config directly to counteract the DiffSuppressFunc above - tier := d.GetRawConfig().GetAttr("tier") - if tier.IsKnown() && !tier.IsNull() { - paramInput.Tier = aws.String(tier.AsString()) + Name: aws.String(d.Id()), + Overwrite: aws.Bool(shouldUpdateParameter(d)), + Tier: awstypes.ParameterTier(d.Get("tier").(string)), + Type: typ, + Value: aws.String(value), } if d.HasChange("data_type") { - paramInput.DataType = aws.String(d.Get("data_type").(string)) + input.DataType = aws.String(d.Get("data_type").(string)) } if d.HasChange(names.AttrDescription) { - paramInput.Description = aws.String(d.Get(names.AttrDescription).(string)) + input.Description = aws.String(d.Get(names.AttrDescription).(string)) } - if d.HasChange("key_id") && d.Get(names.AttrType).(string) == ssm.ParameterTypeSecureString { - paramInput.SetKeyId(d.Get("key_id").(string)) + if d.HasChange("key_id") && typ == awstypes.ParameterTypeSecureString { + input.KeyId = aws.String(d.Get("key_id").(string)) } - _, err := conn.PutParameterWithContext(ctx, paramInput) + // Retrieve the value set in the config directly to counteract the DiffSuppressFunc above. + if v := d.GetRawConfig().GetAttr("tier"); v.IsKnown() && !v.IsNull() { + input.Tier = awstypes.ParameterTier(v.AsString()) + } + + _, err := conn.PutParameter(ctx, input) - if tfawserr.ErrMessageContains(err, "ValidationException", "Tier is not supported") { - log.Printf("[WARN] Updating SSM Parameter (%s): tier %q not supported, using default", d.Get(names.AttrName).(string), d.Get("tier").(string)) - paramInput.Tier = nil - _, err = conn.PutParameterWithContext(ctx, paramInput) + if tfawserr.ErrMessageContains(err, errCodeValidationException, "Tier is not supported") { + log.Printf("[WARN] Creating SSM Parameter (%s): tier %q not supported, using default", d.Id(), d.Get("tier").(string)) + input.Tier = "" + _, err = conn.PutParameter(ctx, input) } if err != nil { @@ -355,13 +332,14 @@ func resourceParameterUpdate(ctx context.Context, d *schema.ResourceData, meta i func resourceParameterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - _, err := conn.DeleteParameterWithContext(ctx, &ssm.DeleteParameterInput{ - Name: aws.String(d.Get(names.AttrName).(string)), + log.Printf("[DEBUG] Deleting SSM Parameter: %s", d.Id()) + _, err := conn.DeleteParameter(ctx, &ssm.DeleteParameterInput{ + Name: aws.String(d.Id()), }) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeParameterNotFound) { + if errs.IsA[*awstypes.ParameterNotFound](err) { return diags } @@ -372,10 +350,77 @@ func resourceParameterDelete(ctx context.Context, d *schema.ResourceData, meta i return diags } -func ShouldUpdateParameter(d *schema.ResourceData) bool { - // If the user has specified a preference, return their preference - if value, ok := d.GetOkExists("overwrite"); ok { - return value.(bool) +func findParameterByName(ctx context.Context, conn *ssm.Client, name string, withDecryption bool) (*awstypes.Parameter, error) { + input := &ssm.GetParameterInput{ + Name: aws.String(name), + WithDecryption: aws.Bool(withDecryption), + } + + output, err := conn.GetParameter(ctx, input) + + if errs.IsA[*awstypes.ParameterNotFound](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Parameter == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.Parameter, nil +} + +func findParameterMetadataByName(ctx context.Context, conn *ssm.Client, name string) (*awstypes.ParameterMetadata, error) { + input := &ssm.DescribeParametersInput{ + ParameterFilters: []awstypes.ParameterStringFilter{ + { + Key: aws.String("Name"), + Option: aws.String("Equals"), + Values: []string{name}, + }, + }, + } + + return findParameterMetadata(ctx, conn, input) +} + +func findParameterMetadata(ctx context.Context, conn *ssm.Client, input *ssm.DescribeParametersInput) (*awstypes.ParameterMetadata, error) { + output, err := findParametersMetadata(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findParametersMetadata(ctx context.Context, conn *ssm.Client, input *ssm.DescribeParametersInput) ([]awstypes.ParameterMetadata, error) { + var output []awstypes.ParameterMetadata + + pages := ssm.NewDescribeParametersPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if err != nil { + return nil, err + } + + output = append(output, page.Parameters...) + } + + return output, nil +} + +func shouldUpdateParameter(d *schema.ResourceData) bool { + // If the user has specified a preference, return their preference. + if v := d.GetRawConfig().GetAttr("overwrite"); v.IsKnown() && !v.IsNull() { + return v.True() } // Since the user has not specified a preference, obey lifecycle rules diff --git a/internal/service/ssm/parameter_test.go b/internal/service/ssm/parameter_test.go index e19e154de40..fc95104f8b4 100644 --- a/internal/service/ssm/parameter_test.go +++ b/internal/service/ssm/parameter_test.go @@ -9,21 +9,22 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "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/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" 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" tfssm "github.com/hashicorp/terraform-provider-aws/internal/service/ssm" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccSSMParameter_basic(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -40,7 +41,7 @@ func TestAccSSMParameter_basic(t *testing.T) { acctest.CheckResourceAttrRegionalARN(resourceName, names.AttrARN, "ssm", fmt.Sprintf("parameter/%s", name)), resource.TestCheckResourceAttr(resourceName, names.AttrValue, "test2"), resource.TestCheckResourceAttr(resourceName, names.AttrType, "String"), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierStandard), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierStandard)), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttrSet(resourceName, names.AttrVersion), resource.TestCheckResourceAttr(resourceName, "data_type", "text"), @@ -60,7 +61,7 @@ func TestAccSSMParameter_basic(t *testing.T) { // TestAccSSMParameter_multiple is mostly a performance benchmark func TestAccSSMParameter_multiple(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_parameter.test" @@ -77,7 +78,7 @@ func TestAccSSMParameter_multiple(t *testing.T) { acctest.CheckResourceAttrRegionalARN(resourceName, names.AttrARN, "ssm", fmt.Sprintf("parameter/%s-1", rName)), resource.TestCheckResourceAttr(resourceName, names.AttrValue, "test2"), resource.TestCheckResourceAttr(resourceName, names.AttrType, "String"), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierStandard), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierStandard)), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttrSet(resourceName, names.AttrVersion), resource.TestCheckResourceAttr(resourceName, "data_type", "text"), @@ -90,7 +91,7 @@ func TestAccSSMParameter_multiple(t *testing.T) { func TestAccSSMParameter_updateValue(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -136,7 +137,7 @@ func TestAccSSMParameter_updateValue(t *testing.T) { func TestAccSSMParameter_updateDescription(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -184,7 +185,7 @@ func TestAccSSMParameter_updateDescription(t *testing.T) { func TestAccSSMParameter_tier(t *testing.T) { ctx := acctest.Context(t) - var parameter1, parameter2, parameter3 ssm.Parameter + var parameter1, parameter2, parameter3 awstypes.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -195,10 +196,10 @@ func TestAccSSMParameter_tier(t *testing.T) { CheckDestroy: testAccCheckParameterDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterConfig_tier(rName, ssm.ParameterTierAdvanced), + Config: testAccParameterConfig_tier(rName, string(awstypes.ParameterTierAdvanced)), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter1), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierAdvanced), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierAdvanced)), ), }, { @@ -208,17 +209,17 @@ func TestAccSSMParameter_tier(t *testing.T) { ImportStateVerifyIgnore: []string{"overwrite"}, }, { - Config: testAccParameterConfig_tier(rName, ssm.ParameterTierStandard), + Config: testAccParameterConfig_tier(rName, string(awstypes.ParameterTierStandard)), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter2), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierStandard), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierStandard)), ), }, { - Config: testAccParameterConfig_tier(rName, ssm.ParameterTierAdvanced), + Config: testAccParameterConfig_tier(rName, string(awstypes.ParameterTierAdvanced)), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter3), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierAdvanced), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierAdvanced)), ), }, }, @@ -227,7 +228,7 @@ func TestAccSSMParameter_tier(t *testing.T) { func TestAccSSMParameter_Tier_intelligentTieringToStandard(t *testing.T) { ctx := acctest.Context(t) - var parameter ssm.Parameter + var parameter awstypes.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -238,10 +239,10 @@ func TestAccSSMParameter_Tier_intelligentTieringToStandard(t *testing.T) { CheckDestroy: testAccCheckParameterDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterConfig_tier(rName, ssm.ParameterTierIntelligentTiering), + Config: testAccParameterConfig_tier(rName, string(awstypes.ParameterTierIntelligentTiering)), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierStandard), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierStandard)), ), }, { @@ -251,17 +252,17 @@ func TestAccSSMParameter_Tier_intelligentTieringToStandard(t *testing.T) { ImportStateVerifyIgnore: []string{"overwrite"}, }, { - Config: testAccParameterConfig_tier(rName, ssm.ParameterTierStandard), + Config: testAccParameterConfig_tier(rName, string(awstypes.ParameterTierStandard)), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierStandard), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierStandard)), ), }, { - Config: testAccParameterConfig_tier(rName, ssm.ParameterTierIntelligentTiering), + Config: testAccParameterConfig_tier(rName, string(awstypes.ParameterTierIntelligentTiering)), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierStandard), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierStandard)), ), }, { @@ -276,7 +277,7 @@ func TestAccSSMParameter_Tier_intelligentTieringToStandard(t *testing.T) { func TestAccSSMParameter_Tier_intelligentTieringToAdvanced(t *testing.T) { ctx := acctest.Context(t) - var parameter1, parameter2 ssm.Parameter + var parameter1, parameter2 awstypes.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -287,10 +288,10 @@ func TestAccSSMParameter_Tier_intelligentTieringToAdvanced(t *testing.T) { CheckDestroy: testAccCheckParameterDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterConfig_tier(rName, ssm.ParameterTierIntelligentTiering), + Config: testAccParameterConfig_tier(rName, string(awstypes.ParameterTierIntelligentTiering)), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter1), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierStandard), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierStandard)), ), }, { @@ -300,18 +301,18 @@ func TestAccSSMParameter_Tier_intelligentTieringToAdvanced(t *testing.T) { ImportStateVerifyIgnore: []string{"overwrite"}, }, { - Config: testAccParameterConfig_tier(rName, ssm.ParameterTierAdvanced), + Config: testAccParameterConfig_tier(rName, string(awstypes.ParameterTierAdvanced)), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter1), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierAdvanced), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierAdvanced)), ), }, { // Intelligent-Tiering will not downgrade an existing parameter to Standard - Config: testAccParameterConfig_tier(rName, ssm.ParameterTierIntelligentTiering), + Config: testAccParameterConfig_tier(rName, string(awstypes.ParameterTierIntelligentTiering)), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter2), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierAdvanced), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierAdvanced)), ), }, { @@ -326,7 +327,7 @@ func TestAccSSMParameter_Tier_intelligentTieringToAdvanced(t *testing.T) { func TestAccSSMParameter_Tier_intelligentTieringOnCreation(t *testing.T) { ctx := acctest.Context(t) - var parameter ssm.Parameter + var parameter awstypes.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -339,10 +340,10 @@ func TestAccSSMParameter_Tier_intelligentTieringOnCreation(t *testing.T) { CheckDestroy: testAccCheckParameterDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterConfig_tierWithValue(rName, ssm.ParameterTierIntelligentTiering, value), + Config: testAccParameterConfig_tierWithValue(rName, string(awstypes.ParameterTierIntelligentTiering), value), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierAdvanced), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierAdvanced)), ), }, { @@ -357,7 +358,7 @@ func TestAccSSMParameter_Tier_intelligentTieringOnCreation(t *testing.T) { func TestAccSSMParameter_Tier_intelligentTieringOnUpdate(t *testing.T) { ctx := acctest.Context(t) - var parameter ssm.Parameter + var parameter awstypes.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -371,17 +372,17 @@ func TestAccSSMParameter_Tier_intelligentTieringOnUpdate(t *testing.T) { CheckDestroy: testAccCheckParameterDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterConfig_tierWithValue(rName, ssm.ParameterTierIntelligentTiering, standardSizedValue), + Config: testAccParameterConfig_tierWithValue(rName, string(awstypes.ParameterTierIntelligentTiering), standardSizedValue), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierStandard), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierStandard)), ), }, { - Config: testAccParameterConfig_tierWithValue(rName, ssm.ParameterTierIntelligentTiering, advancedSizedValue), + Config: testAccParameterConfig_tierWithValue(rName, string(awstypes.ParameterTierIntelligentTiering), advancedSizedValue), Check: resource.ComposeTestCheckFunc( testAccCheckParameterExists(ctx, resourceName, ¶meter), - resource.TestCheckResourceAttr(resourceName, "tier", ssm.ParameterTierAdvanced), + resource.TestCheckResourceAttr(resourceName, "tier", string(awstypes.ParameterTierAdvanced)), ), }, }, @@ -390,7 +391,7 @@ func TestAccSSMParameter_Tier_intelligentTieringOnUpdate(t *testing.T) { func TestAccSSMParameter_disappears(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -414,7 +415,7 @@ func TestAccSSMParameter_disappears(t *testing.T) { func TestAccSSMParameter_Overwrite_basic(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -425,19 +426,19 @@ func TestAccSSMParameter_Overwrite_basic(t *testing.T) { CheckDestroy: testAccCheckParameterDestroy(ctx), Steps: []resource.TestStep{ { - PreConfig: func() { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) input := &ssm.PutParameterInput{ Name: aws.String(fmt.Sprintf("%s-%s", "test_parameter", name)), - Type: aws.String(ssm.ParameterTypeString), + Type: awstypes.ParameterTypeString, Value: aws.String("This value is set using the SDK"), } - _, err := conn.PutParameterWithContext(ctx, input) + _, err := conn.PutParameter(ctx, input) + if err != nil { - t.Fatalf("creating SSM Parameter: (%s):, %s", name, err) + t.Fatal(err) } }, Config: testAccParameterConfig_basicOverwrite(name, "String", "This value is set using Terraform"), @@ -502,7 +503,7 @@ func TestAccSSMParameter_Overwrite_cascade(t *testing.T) { // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/18550 func TestAccSSMParameter_Overwrite_tags(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -533,7 +534,7 @@ func TestAccSSMParameter_Overwrite_tags(t *testing.T) { // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/18550 func TestAccSSMParameter_Overwrite_noOverwriteTags(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -564,7 +565,7 @@ func TestAccSSMParameter_Overwrite_noOverwriteTags(t *testing.T) { // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/18550 func TestAccSSMParameter_Overwrite_updateToTags(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -600,7 +601,7 @@ func TestAccSSMParameter_Overwrite_updateToTags(t *testing.T) { } func TestAccSSMParameter_Overwrite_removeAttribute(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -636,7 +637,7 @@ func TestAccSSMParameter_Overwrite_removeAttribute(t *testing.T) { func TestAccSSMParameter_tags(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter rName := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -683,7 +684,7 @@ func TestAccSSMParameter_tags(t *testing.T) { func TestAccSSMParameter_updateType(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -715,7 +716,7 @@ func TestAccSSMParameter_updateType(t *testing.T) { func TestAccSSMParameter_Overwrite_updateDescription(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -747,7 +748,7 @@ func TestAccSSMParameter_Overwrite_updateDescription(t *testing.T) { func TestAccSSMParameter_changeNameForcesNew(t *testing.T) { ctx := acctest.Context(t) - var beforeParam, afterParam ssm.Parameter + var beforeParam, afterParam awstypes.Parameter before := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) after := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -783,7 +784,7 @@ func TestAccSSMParameter_changeNameForcesNew(t *testing.T) { func TestAccSSMParameter_fullPath(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter name := fmt.Sprintf("/path/%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -814,7 +815,7 @@ func TestAccSSMParameter_fullPath(t *testing.T) { func TestAccSSMParameter_Secure_basic(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter name := fmt.Sprintf("%s_%s", t.Name(), sdkacctest.RandString(10)) resourceName := "aws_ssm_parameter.test" @@ -845,7 +846,7 @@ func TestAccSSMParameter_Secure_basic(t *testing.T) { func TestAccSSMParameter_Secure_insecure(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_parameter.test" @@ -886,7 +887,7 @@ func TestAccSSMParameter_Secure_insecure(t *testing.T) { func TestAccSSMParameter_Secure_insecureChangeSecure(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_parameter.test" @@ -926,7 +927,7 @@ func TestAccSSMParameter_Secure_insecureChangeSecure(t *testing.T) { func TestAccSSMParameter_DataType_ec2Image(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_parameter.test" @@ -955,7 +956,7 @@ func TestAccSSMParameter_DataType_ec2Image(t *testing.T) { func TestAccSSMParameter_DataType_ssmIntegration(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter webhookName := sdkacctest.RandString(16) rName := fmt.Sprintf("/d9d01087-4a3f-49e0-b0b4-d568d7826553/ssm/integrations/webhook/%s", webhookName) resourceName := "aws_ssm_parameter.test" @@ -985,7 +986,7 @@ func TestAccSSMParameter_DataType_ssmIntegration(t *testing.T) { func TestAccSSMParameter_DataType_update(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_parameter.test" @@ -1021,7 +1022,7 @@ func TestAccSSMParameter_DataType_update(t *testing.T) { func TestAccSSMParameter_Secure_key(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter randString := sdkacctest.RandString(10) name := fmt.Sprintf("%s_%s", t.Name(), randString) resourceName := "aws_ssm_parameter.test" @@ -1053,7 +1054,7 @@ func TestAccSSMParameter_Secure_key(t *testing.T) { func TestAccSSMParameter_Secure_keyUpdate(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter randString := sdkacctest.RandString(10) name := fmt.Sprintf("%s_%s", t.Name(), randString) resourceName := "aws_ssm_parameter.test" @@ -1092,8 +1093,7 @@ func TestAccSSMParameter_Secure_keyUpdate(t *testing.T) { }) } -func testAccCheckParameterRecreated(t *testing.T, - before, after *ssm.Parameter) resource.TestCheckFunc { +func testAccCheckParameterRecreated(t *testing.T, before, after *awstypes.Parameter) resource.TestCheckFunc { return func(s *terraform.State) error { if *before.Name == *after.Name { t.Fatalf("Expected change of SSM Param Names, but both were %v", *before.Name) @@ -1102,36 +1102,22 @@ func testAccCheckParameterRecreated(t *testing.T, } } -func testAccCheckParameterExists(ctx context.Context, n string, param *ssm.Parameter) resource.TestCheckFunc { +func testAccCheckParameterExists(ctx context.Context, n string, v *awstypes.Parameter) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No SSM Parameter ID is set") - } + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + output, err := tfssm.FindParameterByName(ctx, conn, rs.Primary.ID, true) - paramInput := &ssm.GetParametersInput{ - Names: []*string{ - aws.String(rs.Primary.Attributes[names.AttrName]), - }, - WithDecryption: aws.Bool(true), - } - - resp, err := conn.GetParametersWithContext(ctx, paramInput) if err != nil { return err } - if len(resp.Parameters) == 0 { - return fmt.Errorf("Expected AWS SSM Parameter to be created, but wasn't found") - } - - *param = *resp.Parameters[0] + *v = *output return nil } @@ -1139,34 +1125,24 @@ func testAccCheckParameterExists(ctx context.Context, n string, param *ssm.Param func testAccCheckParameterDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ssm_parameter" { continue } - paramInput := &ssm.GetParametersInput{ - Names: []*string{ - aws.String(rs.Primary.Attributes[names.AttrName]), - }, - } - - resp, err := conn.GetParametersWithContext(ctx, paramInput) + _, err := tfssm.FindParameterByName(ctx, conn, rs.Primary.ID, false) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeParameterNotFound) { + if tfresource.NotFound(err) { continue } if err != nil { - return fmt.Errorf("error reading SSM Parameter (%s): %w", rs.Primary.ID, err) - } - - if resp == nil || len(resp.Parameters) == 0 { - continue + return err } - return fmt.Errorf("Expected AWS SSM Parameter to be gone, but was still found") + return fmt.Errorf("SSM Parameter %s still exists", rs.Primary.ID) } return nil @@ -1528,37 +1504,3 @@ resource "aws_kms_alias" "test_alias" { } `, rName, value, keyAlias) } - -func TestParameterShouldUpdate(t *testing.T) { - t.Parallel() - - data := tfssm.ResourceParameter().TestResourceData() - failure := false - - if !tfssm.ShouldUpdateParameter(data) { - t.Logf("Existing resources should be overwritten if the values don't match!") - failure = true - } - - data.MarkNewResource() - if tfssm.ShouldUpdateParameter(data) { - t.Logf("New resources must never be overwritten, this will overwrite parameters created outside of the system") - failure = true - } - - data = tfssm.ResourceParameter().TestResourceData() - data.Set("overwrite", true) - if !tfssm.ShouldUpdateParameter(data) { - t.Logf("Resources should always be overwritten if the user requests it") - failure = true - } - - data.Set("overwrite", false) - if tfssm.ShouldUpdateParameter(data) { - t.Logf("Resources should never be overwritten if the user requests it") - failure = true - } - if failure { - t.Fail() - } -} diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index ba90ff00542..fd984520096 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -101,7 +101,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Maintenance Window Task", }, { - Factory: ResourceParameter, + Factory: resourceParameter, TypeName: "aws_ssm_parameter", Name: "Parameter", Tags: &types.ServicePackageResourceTags{ From 77fe1405c1d2650a832fc935c299b42013e14ef4 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 12:12:03 -0400 Subject: [PATCH 15/34] d/aws_ssm_parameter: Migrate to AWS SDK for Go v2. --- internal/service/ssm/parameter_data_source.go | 39 +++++++------------ .../service/ssm/parameter_data_source_test.go | 16 ++++---- internal/service/ssm/service_package_gen.go | 3 +- 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/internal/service/ssm/parameter_data_source.go b/internal/service/ssm/parameter_data_source.go index 673945dee0f..fcbfdeea01e 100644 --- a/internal/service/ssm/parameter_data_source.go +++ b/internal/service/ssm/parameter_data_source.go @@ -5,10 +5,9 @@ package ssm import ( "context" - "log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go-v2/aws" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -16,10 +15,11 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_ssm_parameter") -func DataSourceParameter() *schema.Resource { +// @SDKDataSource("aws_ssm_parameter", name="Parameter") +func dataSourceParameter() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataParameterRead, + Schema: map[string]*schema.Schema{ names.AttrARN: { Type: schema.TypeString, @@ -42,48 +42,39 @@ func DataSourceParameter() *schema.Resource { Computed: true, Sensitive: true, }, + names.AttrVersion: { + Type: schema.TypeInt, + Computed: true, + }, "with_decryption": { Type: schema.TypeBool, Optional: true, Default: true, }, - names.AttrVersion: { - Type: schema.TypeInt, - Computed: true, - }, }, } } func dataParameterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) name := d.Get(names.AttrName).(string) - - paramInput := &ssm.GetParameterInput{ - Name: aws.String(name), - WithDecryption: aws.Bool(d.Get("with_decryption").(bool)), - } - - log.Printf("[DEBUG] Reading SSM Parameter: %s", paramInput) - resp, err := conn.GetParameterWithContext(ctx, paramInput) + param, err := findParameterByName(ctx, conn, name, d.Get("with_decryption").(bool)) if err != nil { - return sdkdiag.AppendErrorf(diags, "describing SSM parameter (%s): %s", name, err) + return sdkdiag.AppendErrorf(diags, "reading SSM Parameter (%s): %s", name, err) } - param := resp.Parameter - - d.SetId(aws.StringValue(param.Name)) + d.SetId(aws.ToString(param.Name)) d.Set(names.AttrARN, param.ARN) - d.Set(names.AttrValue, param.Value) d.Set("insecure_value", nil) - if aws.StringValue(param.Type) != ssm.ParameterTypeSecureString { + if param.Type != awstypes.ParameterTypeSecureString { d.Set("insecure_value", param.Value) } d.Set(names.AttrName, param.Name) d.Set(names.AttrType, param.Type) + d.Set(names.AttrValue, param.Value) d.Set(names.AttrVersion, param.Version) return diags diff --git a/internal/service/ssm/parameter_data_source_test.go b/internal/service/ssm/parameter_data_source_test.go index 1dc52382fa5..315808871a2 100644 --- a/internal/service/ssm/parameter_data_source_test.go +++ b/internal/service/ssm/parameter_data_source_test.go @@ -7,7 +7,7 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/acctest" @@ -25,7 +25,7 @@ func TestAccSSMParameterDataSource_basic(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccParameterDataSourceConfig_basic(name, "false"), + Config: testAccParameterDataSourceConfig_basic(name, false), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(resourceName, names.AttrARN, "aws_ssm_parameter.test", names.AttrARN), resource.TestCheckResourceAttr(resourceName, names.AttrName, name), @@ -36,7 +36,7 @@ func TestAccSSMParameterDataSource_basic(t *testing.T) { ), }, { - Config: testAccParameterDataSourceConfig_basic(name, "true"), + Config: testAccParameterDataSourceConfig_basic(name, true), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(resourceName, names.AttrARN, "aws_ssm_parameter.test", names.AttrARN), resource.TestCheckResourceAttr(resourceName, names.AttrName, name), @@ -60,7 +60,7 @@ func TestAccSSMParameterDataSource_fullPath(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccParameterDataSourceConfig_basic(name, "false"), + Config: testAccParameterDataSourceConfig_basic(name, false), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(resourceName, names.AttrARN, "aws_ssm_parameter.test", names.AttrARN), resource.TestCheckResourceAttr(resourceName, names.AttrName, name), @@ -75,7 +75,7 @@ func TestAccSSMParameterDataSource_fullPath(t *testing.T) { func TestAccSSMParameterDataSource_insecureValue(t *testing.T) { ctx := acctest.Context(t) - var param ssm.Parameter + var param awstypes.Parameter rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ssm_parameter.test" dataSourceName := "data.aws_ssm_parameter.test" @@ -97,17 +97,17 @@ func TestAccSSMParameterDataSource_insecureValue(t *testing.T) { }) } -func testAccParameterDataSourceConfig_basic(name string, withDecryption string) string { +func testAccParameterDataSourceConfig_basic(name string, withDecryption bool) string { return fmt.Sprintf(` resource "aws_ssm_parameter" "test" { - name = "%s" + name = %[1]q type = "String" value = "TestValue" } data "aws_ssm_parameter" "test" { name = aws_ssm_parameter.test.name - with_decryption = %s + with_decryption = %[2]t } `, name, withDecryption) } diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index fd984520096..0ef721a53cd 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -40,8 +40,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Name: "Maintenance Windows", }, { - Factory: DataSourceParameter, + Factory: dataSourceParameter, TypeName: "aws_ssm_parameter", + Name: "Parameter", }, { Factory: DataSourceParametersByPath, From 879648d74621c234699ec277155492382d14d0cb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 12:20:45 -0400 Subject: [PATCH 16/34] d/aws_ssm_parameters_by_path: Migrate to AWS SDK for Go v2. --- .../ssm/parameters_by_path_data_source.go | 54 +++++++++---------- internal/service/ssm/service_package_gen.go | 3 +- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/internal/service/ssm/parameters_by_path_data_source.go b/internal/service/ssm/parameters_by_path_data_source.go index ed246f0fc65..5ae3f8ac627 100644 --- a/internal/service/ssm/parameters_by_path_data_source.go +++ b/internal/service/ssm/parameters_by_path_data_source.go @@ -6,17 +6,19 @@ package ssm import ( "context" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_ssm_parameters_by_path") -func DataSourceParametersByPath() *schema.Resource { +// @SDKDataSource("aws_ssm_parameters_by_path", name="Parameters By Path") +func dataSourceParametersByPath() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceParametersReadByPath, @@ -62,7 +64,7 @@ func DataSourceParametersByPath() *schema.Resource { func dataSourceParametersReadByPath(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) path := d.Get(names.AttrPath).(string) input := &ssm.GetParametersByPathInput{ @@ -70,36 +72,32 @@ func dataSourceParametersReadByPath(ctx context.Context, d *schema.ResourceData, Recursive: aws.Bool(d.Get("recursive").(bool)), WithDecryption: aws.Bool(d.Get("with_decryption").(bool)), } + var output []awstypes.Parameter - arns := make([]string, 0) - n := make([]string, 0) - types := make([]string, 0) - values := make([]string, 0) + pages := ssm.NewGetParametersByPathPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) - err := conn.GetParametersByPathPagesWithContext(ctx, input, func(page *ssm.GetParametersByPathOutput, lastPage bool) bool { - if page == nil { - return !lastPage + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading SSM Parameters by path (%s): %s", path, err) } - for _, param := range page.Parameters { - arns = append(arns, aws.StringValue(param.ARN)) - n = append(n, aws.StringValue(param.Name)) - types = append(types, aws.StringValue(param.Type)) - values = append(values, aws.StringValue(param.Value)) - } - - return !lastPage - }) - - if err != nil { - return sdkdiag.AppendErrorf(diags, "getting SSM parameters by path (%s): %s", path, err) + output = append(output, page.Parameters...) } d.SetId(path) - d.Set(names.AttrARNs, arns) - d.Set("names", n) - d.Set("types", types) - d.Set(names.AttrValues, values) + d.Set(names.AttrARNs, tfslices.ApplyToAll(output, func(v awstypes.Parameter) string { + return aws.ToString(v.ARN) + })) + d.Set("names", tfslices.ApplyToAll(output, func(v awstypes.Parameter) string { + return aws.ToString(v.Name) + })) + d.Set("types", tfslices.ApplyToAll(output, func(v awstypes.Parameter) awstypes.ParameterType { + return v.Type + })) + d.Set(names.AttrValues, tfslices.ApplyToAll(output, func(v awstypes.Parameter) string { + return aws.ToString(v.Value) + })) return diags } diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index 0ef721a53cd..2c144e78b9a 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -45,8 +45,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Name: "Parameter", }, { - Factory: DataSourceParametersByPath, + Factory: dataSourceParametersByPath, TypeName: "aws_ssm_parameters_by_path", + Name: "Parameters By Path", }, { Factory: DataSourcePatchBaseline, From 8873dc8344749620194563c1cca87a219d0c1c07 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 14:14:40 -0400 Subject: [PATCH 17/34] r/aws_ssm_patch_baseline: Migrate to AWS SDK for Go v2. --- internal/service/ssm/patch_baseline.go | 314 ++++++++++---------- internal/service/ssm/patch_baseline_test.go | 57 ++-- 2 files changed, 180 insertions(+), 191 deletions(-) diff --git a/internal/service/ssm/patch_baseline.go b/internal/service/ssm/patch_baseline.go index 61d89fcc60a..bd6731f2179 100644 --- a/internal/service/ssm/patch_baseline.go +++ b/internal/service/ssm/patch_baseline.go @@ -6,25 +6,24 @@ package ssm import ( "context" "encoding/json" - "fmt" "log" "strings" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go-v2/service/ssm/types" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/service/ssm" - "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/aws/arn" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "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/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" - "github.com/hashicorp/terraform-provider-aws/internal/sdkv2" 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" @@ -61,10 +60,10 @@ func resourcePatchBaseline() *schema.Resource { ValidateFunc: validation.StringMatch(regexache.MustCompile(`([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))`), "must be formatted YYYY-MM-DD"), }, "compliance_level": { - Type: schema.TypeString, - Optional: true, - Default: ssm.PatchComplianceLevelUnspecified, - ValidateFunc: validation.StringInSlice(ssm.PatchComplianceLevel_Values(), false), + Type: schema.TypeString, + Optional: true, + Default: awstypes.PatchComplianceLevelUnspecified, + ValidateDiagFunc: enum.Validate[awstypes.PatchComplianceLevel](), }, "enable_non_security": { Type: schema.TypeBool, @@ -78,9 +77,9 @@ func resourcePatchBaseline() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ names.AttrKey: { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(ssm.PatchFilterKey_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.PatchFilterKey](), }, names.AttrValues: { Type: schema.TypeList, @@ -108,10 +107,10 @@ func resourcePatchBaseline() *schema.Resource { }, }, "approved_patches_compliance_level": { - Type: schema.TypeString, - Optional: true, - Default: ssm.PatchComplianceLevelUnspecified, - ValidateFunc: validation.StringInSlice(ssm.PatchComplianceLevel_Values(), false), + Type: schema.TypeString, + Optional: true, + Default: awstypes.PatchComplianceLevelUnspecified, + ValidateDiagFunc: enum.Validate[awstypes.PatchComplianceLevel](), }, "approved_patches_enable_non_security": { Type: schema.TypeBool, @@ -133,9 +132,9 @@ func resourcePatchBaseline() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ names.AttrKey: { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(ssm.PatchFilterKey_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[awstypes.PatchFilterKey](), }, names.AttrValues: { Type: schema.TypeList, @@ -163,11 +162,11 @@ func resourcePatchBaseline() *schema.Resource { ), }, "operating_system": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: ssm.OperatingSystemWindows, - ValidateFunc: validation.StringInSlice(ssm.OperatingSystem_Values(), false), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: awstypes.OperatingSystemWindows, + ValidateDiagFunc: enum.Validate[awstypes.OperatingSystem](), }, "rejected_patches": { Type: schema.TypeSet, @@ -179,10 +178,10 @@ func resourcePatchBaseline() *schema.Resource { }, }, "rejected_patches_action": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice(ssm.PatchAction_Values(), false), + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: enum.Validate[awstypes.PatchAction](), }, names.AttrSource: { Type: schema.TypeList, @@ -220,7 +219,23 @@ func resourcePatchBaseline() *schema.Resource { }, CustomizeDiff: customdiff.Sequence( - resourceObjectCustomizeDiff, + func(_ context.Context, d *schema.ResourceDiff, meta interface{}) error { + if d.HasChanges(names.AttrDescription, + "global_filter", + "approval_rule", + "approved_patches", + "rejected_patches", + "operating_system", + "approved_patches_compliance_level", + "rejected_patches_action", + "approved_patches_enable_non_security", + names.AttrSource, + ) { + return d.SetNewComputed("json") + } + + return nil + }, verify.SetTagsDiff, ), } @@ -228,13 +243,13 @@ func resourcePatchBaseline() *schema.Resource { func resourcePatchBaselineCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) name := d.Get(names.AttrName).(string) input := &ssm.CreatePatchBaselineInput{ - ApprovedPatchesComplianceLevel: aws.String(d.Get("approved_patches_compliance_level").(string)), + ApprovedPatchesComplianceLevel: awstypes.PatchComplianceLevel(d.Get("approved_patches_compliance_level").(string)), Name: aws.String(name), - OperatingSystem: aws.String(d.Get("operating_system").(string)), + OperatingSystem: awstypes.OperatingSystem(d.Get("operating_system").(string)), Tags: getTagsIn(ctx), } @@ -243,7 +258,7 @@ func resourcePatchBaselineCreate(ctx context.Context, d *schema.ResourceData, me } if v, ok := d.GetOk("approved_patches"); ok && v.(*schema.Set).Len() > 0 { - input.ApprovedPatches = flex.ExpandStringSet(v.(*schema.Set)) + input.ApprovedPatches = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("approved_patches_enable_non_security"); ok { @@ -259,31 +274,31 @@ func resourcePatchBaselineCreate(ctx context.Context, d *schema.ResourceData, me } if v, ok := d.GetOk("rejected_patches"); ok && v.(*schema.Set).Len() > 0 { - input.RejectedPatches = flex.ExpandStringSet(v.(*schema.Set)) + input.RejectedPatches = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("rejected_patches_action"); ok { - input.RejectedPatchesAction = aws.String(v.(string)) + input.RejectedPatchesAction = awstypes.PatchAction(v.(string)) } if _, ok := d.GetOk(names.AttrSource); ok { input.Sources = expandPatchSource(d) } - output, err := conn.CreatePatchBaselineWithContext(ctx, input) + output, err := conn.CreatePatchBaseline(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "creating SSM Patch Baseline (%s): %s", name, err) } - d.SetId(aws.StringValue(output.BaselineId)) + d.SetId(aws.ToString(output.BaselineId)) return append(diags, resourcePatchBaselineRead(ctx, d, meta)...) } func resourcePatchBaselineRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) output, err := findPatchBaselineByID(ctx, conn, d.Id()) @@ -297,14 +312,6 @@ func resourcePatchBaselineRead(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "reading SSM Patch Baseline (%s): %s", d.Id(), err) } - arn := arn.ARN{ - Partition: meta.(*conns.AWSClient).Partition, - Region: meta.(*conns.AWSClient).Region, - Service: "ssm", - AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("patchbaseline/%s", strings.TrimPrefix(d.Id(), "/")), - }.String() - jsonDoc, err := json.MarshalIndent(output, "", " ") if err != nil { return sdkdiag.AppendFromErr(diags, err) @@ -314,9 +321,16 @@ func resourcePatchBaselineRead(ctx context.Context, d *schema.ResourceData, meta if err := d.Set("approval_rule", flattenPatchRuleGroup(output.ApprovalRules)); err != nil { return sdkdiag.AppendErrorf(diags, "setting approval_rule: %s", err) } - d.Set("approved_patches", aws.StringValueSlice(output.ApprovedPatches)) + d.Set("approved_patches", output.ApprovedPatches) d.Set("approved_patches_compliance_level", output.ApprovedPatchesComplianceLevel) d.Set("approved_patches_enable_non_security", output.ApprovedPatchesEnableNonSecurity) + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Region: meta.(*conns.AWSClient).Region, + Service: "ssm", + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: "patchbaseline/" + strings.TrimPrefix(d.Id(), "/"), + }.String() d.Set(names.AttrARN, arn) d.Set(names.AttrDescription, output.Description) if err := d.Set("global_filter", flattenPatchFilterGroup(output.GlobalFilters)); err != nil { @@ -325,7 +339,7 @@ func resourcePatchBaselineRead(ctx context.Context, d *schema.ResourceData, meta d.Set("json", jsonString) d.Set(names.AttrName, output.Name) d.Set("operating_system", output.OperatingSystem) - d.Set("rejected_patches", aws.StringValueSlice(output.RejectedPatches)) + d.Set("rejected_patches", output.RejectedPatches) d.Set("rejected_patches_action", output.RejectedPatchesAction) if err := d.Set(names.AttrSource, flattenPatchSource(output.Sources)); err != nil { return sdkdiag.AppendErrorf(diags, "setting source: %s", err) @@ -336,7 +350,7 @@ func resourcePatchBaselineRead(ctx context.Context, d *schema.ResourceData, meta func resourcePatchBaselineUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) if d.HasChangesExcept(names.AttrTags, names.AttrTagsAll) { input := &ssm.UpdatePatchBaselineInput{ @@ -348,11 +362,11 @@ func resourcePatchBaselineUpdate(ctx context.Context, d *schema.ResourceData, me } if d.HasChange("approved_patches") { - input.ApprovedPatches = flex.ExpandStringSet(d.Get("approved_patches").(*schema.Set)) + input.ApprovedPatches = flex.ExpandStringValueSet(d.Get("approved_patches").(*schema.Set)) } if d.HasChange("approved_patches_compliance_level") { - input.ApprovedPatchesComplianceLevel = aws.String(d.Get("approved_patches_compliance_level").(string)) + input.ApprovedPatchesComplianceLevel = awstypes.PatchComplianceLevel(d.Get("approved_patches_compliance_level").(string)) } if d.HasChange("approved_patches_enable_non_security") { @@ -372,18 +386,18 @@ func resourcePatchBaselineUpdate(ctx context.Context, d *schema.ResourceData, me } if d.HasChange("rejected_patches") { - input.RejectedPatches = flex.ExpandStringSet(d.Get("rejected_patches").(*schema.Set)) + input.RejectedPatches = flex.ExpandStringValueSet(d.Get("rejected_patches").(*schema.Set)) } if d.HasChange("rejected_patches_action") { - input.RejectedPatchesAction = aws.String(d.Get("rejected_patches_action").(string)) + input.RejectedPatchesAction = awstypes.PatchAction(d.Get("rejected_patches_action").(string)) } if d.HasChange(names.AttrSource) { input.Sources = expandPatchSource(d) } - _, err := conn.UpdatePatchBaselineWithContext(ctx, input) + _, err := conn.UpdatePatchBaseline(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "updating SSM Patch Baseline (%s): %s", d.Id(), err) @@ -394,23 +408,23 @@ func resourcePatchBaselineUpdate(ctx context.Context, d *schema.ResourceData, me } func resourcePatchBaselineDelete(ctx context.Context, d *schema.ResourceData, meta any) (diags diag.Diagnostics) { - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) log.Printf("[INFO] Deleting SSM Patch Baseline: %s", d.Id()) input := &ssm.DeletePatchBaselineInput{ BaselineId: aws.String(d.Id()), } - _, err := conn.DeletePatchBaselineWithContext(ctx, input) + _, err := conn.DeletePatchBaseline(ctx, input) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeResourceInUseException) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { // Reset the default patch baseline before retrying. - diags = append(diags, defaultPatchBaselineRestoreOSDefault(ctx, meta.(*conns.AWSClient).SSMClient(ctx), types.OperatingSystem(d.Get("operating_system").(string)))...) + diags = append(diags, defaultPatchBaselineRestoreOSDefault(ctx, meta.(*conns.AWSClient).SSMClient(ctx), awstypes.OperatingSystem(d.Get("operating_system").(string)))...) if diags.HasError() { return } - _, err = conn.DeletePatchBaselineWithContext(ctx, input) + _, err = conn.DeletePatchBaseline(ctx, input) } if err != nil { @@ -420,14 +434,14 @@ func resourcePatchBaselineDelete(ctx context.Context, d *schema.ResourceData, me return } -func findPatchBaselineByID(ctx context.Context, conn *ssm.SSM, id string) (*ssm.GetPatchBaselineOutput, error) { +func findPatchBaselineByID(ctx context.Context, conn *ssm.Client, id string) (*ssm.GetPatchBaselineOutput, error) { input := &ssm.GetPatchBaselineInput{ BaselineId: aws.String(id), } - output, err := conn.GetPatchBaselineWithContext(ctx, input) + output, err := conn.GetPatchBaseline(ctx, input) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeDoesNotExistException) { + if errs.IsA[*awstypes.DoesNotExistException](err) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, @@ -445,180 +459,154 @@ func findPatchBaselineByID(ctx context.Context, conn *ssm.SSM, id string) (*ssm. return output, nil } -func expandPatchFilterGroup(d *schema.ResourceData) *ssm.PatchFilterGroup { - var filters []*ssm.PatchFilter +func expandPatchFilterGroup(d *schema.ResourceData) *awstypes.PatchFilterGroup { + var filters []awstypes.PatchFilter - filterConfig := d.Get("global_filter").([]interface{}) + tfList := d.Get("global_filter").([]interface{}) - for _, fConfig := range filterConfig { - config := fConfig.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) - filter := &ssm.PatchFilter{ - Key: aws.String(config[names.AttrKey].(string)), - Values: flex.ExpandStringList(config[names.AttrValues].([]interface{})), + filter := awstypes.PatchFilter{ + Key: awstypes.PatchFilterKey(tfMap[names.AttrKey].(string)), + Values: flex.ExpandStringValueList(tfMap[names.AttrValues].([]interface{})), } filters = append(filters, filter) } - return &ssm.PatchFilterGroup{ + return &awstypes.PatchFilterGroup{ PatchFilters: filters, } } -func flattenPatchFilterGroup(group *ssm.PatchFilterGroup) []map[string]interface{} { - if len(group.PatchFilters) == 0 { +func flattenPatchFilterGroup(apiObject *awstypes.PatchFilterGroup) []interface{} { + if len(apiObject.PatchFilters) == 0 { return nil } - result := make([]map[string]interface{}, 0, len(group.PatchFilters)) + tfList := make([]interface{}, 0, len(apiObject.PatchFilters)) - for _, filter := range group.PatchFilters { - f := make(map[string]interface{}) - f[names.AttrKey] = aws.StringValue(filter.Key) - f[names.AttrValues] = flex.FlattenStringList(filter.Values) + for _, apiObject := range apiObject.PatchFilters { + tfMap := make(map[string]interface{}) + tfMap[names.AttrKey] = apiObject.Key + tfMap[names.AttrValues] = apiObject.Values - result = append(result, f) + tfList = append(tfList, tfMap) } - return result + return tfList } -func expandPatchRuleGroup(d *schema.ResourceData) *ssm.PatchRuleGroup { - var rules []*ssm.PatchRule +func expandPatchRuleGroup(d *schema.ResourceData) *awstypes.PatchRuleGroup { + var rules []awstypes.PatchRule - ruleConfig := d.Get("approval_rule").([]interface{}) + tfList := d.Get("approval_rule").([]interface{}) - for _, rConfig := range ruleConfig { - rCfg := rConfig.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) - var filters []*ssm.PatchFilter - filterConfig := rCfg["patch_filter"].([]interface{}) + var filters []awstypes.PatchFilter + tfList := tfMap["patch_filter"].([]interface{}) - for _, fConfig := range filterConfig { - fCfg := fConfig.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) - filter := &ssm.PatchFilter{ - Key: aws.String(fCfg[names.AttrKey].(string)), - Values: flex.ExpandStringList(fCfg[names.AttrValues].([]interface{})), + filter := awstypes.PatchFilter{ + Key: awstypes.PatchFilterKey(tfMap[names.AttrKey].(string)), + Values: flex.ExpandStringValueList(tfMap[names.AttrValues].([]interface{})), } filters = append(filters, filter) } - filterGroup := &ssm.PatchFilterGroup{ + filterGroup := &awstypes.PatchFilterGroup{ PatchFilters: filters, } - rule := &ssm.PatchRule{ + rule := awstypes.PatchRule{ + ComplianceLevel: awstypes.PatchComplianceLevel(tfMap["compliance_level"].(string)), + EnableNonSecurity: aws.Bool(tfMap["enable_non_security"].(bool)), PatchFilterGroup: filterGroup, - ComplianceLevel: aws.String(rCfg["compliance_level"].(string)), - EnableNonSecurity: aws.Bool(rCfg["enable_non_security"].(bool)), } - if v, ok := rCfg["approve_until_date"].(string); ok && v != "" { + if v, ok := tfMap["approve_until_date"].(string); ok && v != "" { rule.ApproveUntilDate = aws.String(v) - } else if v, ok := rCfg["approve_after_days"].(int); ok { - rule.ApproveAfterDays = aws.Int64(int64(v)) + } else if v, ok := tfMap["approve_after_days"].(int); ok { + rule.ApproveAfterDays = aws.Int32(int32(v)) } rules = append(rules, rule) } - return &ssm.PatchRuleGroup{ + return &awstypes.PatchRuleGroup{ PatchRules: rules, } } -func flattenPatchRuleGroup(group *ssm.PatchRuleGroup) []map[string]interface{} { - if len(group.PatchRules) == 0 { +func flattenPatchRuleGroup(apiObject *awstypes.PatchRuleGroup) []interface{} { + if len(apiObject.PatchRules) == 0 { return nil } - result := make([]map[string]interface{}, 0, len(group.PatchRules)) + tfList := make([]interface{}, 0, len(apiObject.PatchRules)) - for _, rule := range group.PatchRules { - r := make(map[string]interface{}) - r["compliance_level"] = aws.StringValue(rule.ComplianceLevel) - r["enable_non_security"] = aws.BoolValue(rule.EnableNonSecurity) - r["patch_filter"] = flattenPatchFilterGroup(rule.PatchFilterGroup) + for _, apiObject := range apiObject.PatchRules { + tfMap := make(map[string]interface{}) + tfMap["compliance_level"] = apiObject.ComplianceLevel + tfMap["enable_non_security"] = aws.ToBool(apiObject.EnableNonSecurity) + tfMap["patch_filter"] = flattenPatchFilterGroup(apiObject.PatchFilterGroup) - if rule.ApproveAfterDays != nil { - r["approve_after_days"] = aws.Int64Value(rule.ApproveAfterDays) + if apiObject.ApproveAfterDays != nil { + tfMap["approve_after_days"] = aws.ToInt32(apiObject.ApproveAfterDays) } - if rule.ApproveUntilDate != nil { - r["approve_until_date"] = aws.StringValue(rule.ApproveUntilDate) + if apiObject.ApproveUntilDate != nil { + tfMap["approve_until_date"] = aws.ToString(apiObject.ApproveUntilDate) } - result = append(result, r) + tfList = append(tfList, tfMap) } - return result + return tfList } -func expandPatchSource(d *schema.ResourceData) []*ssm.PatchSource { - var sources []*ssm.PatchSource +func expandPatchSource(d *schema.ResourceData) []awstypes.PatchSource { + var apiObjects []awstypes.PatchSource - sourceConfigs := d.Get(names.AttrSource).([]interface{}) + tfList := d.Get(names.AttrSource).([]interface{}) - for _, sConfig := range sourceConfigs { - config := sConfig.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap := tfMapRaw.(map[string]interface{}) - source := &ssm.PatchSource{ - Name: aws.String(config[names.AttrName].(string)), - Configuration: aws.String(config[names.AttrConfiguration].(string)), - Products: flex.ExpandStringList(config["products"].([]interface{})), + apiObject := awstypes.PatchSource{ + Configuration: aws.String(tfMap[names.AttrConfiguration].(string)), + Name: aws.String(tfMap[names.AttrName].(string)), + Products: flex.ExpandStringValueList(tfMap["products"].([]interface{})), } - sources = append(sources, source) + apiObjects = append(apiObjects, apiObject) } - return sources + return apiObjects } -func flattenPatchSource(sources []*ssm.PatchSource) []map[string]interface{} { - if len(sources) == 0 { +func flattenPatchSource(apiObjects []awstypes.PatchSource) []interface{} { + if len(apiObjects) == 0 { return nil } - result := make([]map[string]interface{}, 0, len(sources)) + tfList := make([]interface{}, 0, len(apiObjects)) - for _, source := range sources { - s := make(map[string]interface{}) - s[names.AttrName] = aws.StringValue(source.Name) - s[names.AttrConfiguration] = aws.StringValue(source.Configuration) - s["products"] = flex.FlattenStringList(source.Products) - result = append(result, s) - } + for _, apiObject := range apiObjects { + tfMap := make(map[string]interface{}) - return result -} + tfMap[names.AttrConfiguration] = aws.ToString(apiObject.Configuration) + tfMap[names.AttrName] = aws.ToString(apiObject.Name) + tfMap["products"] = apiObject.Products -func resourceObjectCustomizeDiff(_ context.Context, d *schema.ResourceDiff, meta interface{}) error { - if hasObjectContentChanges(d) { - return d.SetNewComputed("json") + tfList = append(tfList, tfMap) } - return nil -} - -func hasObjectContentChanges(d sdkv2.ResourceDiffer) bool { - for _, key := range []string{ - names.AttrDescription, - "global_filter", - "approval_rule", - "approved_patches", - "rejected_patches", - "operating_system", - "approved_patches_compliance_level", - "rejected_patches_action", - "approved_patches_enable_non_security", - names.AttrSource, - } { - if d.HasChange(key) { - return true - } - } - return false + return tfList } diff --git a/internal/service/ssm/patch_baseline_test.go b/internal/service/ssm/patch_baseline_test.go index c66dd9b668e..0ad88a5fa97 100644 --- a/internal/service/ssm/patch_baseline_test.go +++ b/internal/service/ssm/patch_baseline_test.go @@ -9,8 +9,9 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" @@ -48,7 +49,7 @@ func TestAccSSMPatchBaseline_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "approved_patches.#", "1"), resource.TestCheckTypeSetElemAttr(resourceName, "approved_patches.*", "KB123456"), resource.TestCheckResourceAttr(resourceName, names.AttrName, fmt.Sprintf("patch-baseline-%s", name)), - resource.TestCheckResourceAttr(resourceName, "approved_patches_compliance_level", ssm.PatchComplianceLevelCritical), + resource.TestCheckResourceAttr(resourceName, "approved_patches_compliance_level", string(awstypes.PatchComplianceLevelCritical)), resource.TestCheckResourceAttr(resourceName, names.AttrDescription, "Baseline containing all updates approved for production systems"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "approved_patches_enable_non_security", "false"), @@ -80,14 +81,14 @@ func TestAccSSMPatchBaseline_basic(t *testing.T) { resource.TestCheckTypeSetElemAttr(resourceName, "approved_patches.*", "KB123456"), resource.TestCheckTypeSetElemAttr(resourceName, "approved_patches.*", "KB456789"), resource.TestCheckResourceAttr(resourceName, names.AttrName, fmt.Sprintf("updated-patch-baseline-%s", name)), - resource.TestCheckResourceAttr(resourceName, "approved_patches_compliance_level", ssm.PatchComplianceLevelHigh), + resource.TestCheckResourceAttr(resourceName, "approved_patches_compliance_level", string(awstypes.PatchComplianceLevelHigh)), resource.TestCheckResourceAttr(resourceName, names.AttrDescription, "Baseline containing all updates approved for production systems - August 2017"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), acctest.CheckResourceAttrJMESPair(resourceName, "json", "ApprovedPatches[0]", resourceName, "approved_patches.1"), acctest.CheckResourceAttrJMESPair(resourceName, "json", "ApprovedPatches[1]", resourceName, "approved_patches.0"), acctest.CheckResourceAttrJMES(resourceName, "json", "ApprovedPatches|length(@)", "2"), func(*terraform.State) error { - if aws.StringValue(before.BaselineId) != aws.StringValue(after.BaselineId) { + if aws.ToString(before.BaselineId) != aws.ToString(after.BaselineId) { t.Fatal("Baseline IDs changed unexpectedly") } return nil @@ -110,7 +111,7 @@ func TestAccSSMPatchBaseline_tags(t *testing.T) { CheckDestroy: testAccCheckPatchBaselineDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccPatchBaselineConfig_basicTags1(name, "key1", "value1"), + Config: testAccPatchBaselineConfig_tags1(name, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckPatchBaselineExists(ctx, resourceName, &patch), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -123,7 +124,7 @@ func TestAccSSMPatchBaseline_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccPatchBaselineConfig_basicTags2(name, "key1", "value1updated", "key2", "value2"), + Config: testAccPatchBaselineConfig_tags2(name, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckPatchBaselineExists(ctx, resourceName, &patch), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), @@ -132,7 +133,7 @@ func TestAccSSMPatchBaseline_tags(t *testing.T) { ), }, { - Config: testAccPatchBaselineConfig_basicTags1(name, "key2", "value2"), + Config: testAccPatchBaselineConfig_tags1(name, "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckPatchBaselineExists(ctx, resourceName, &patch), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), @@ -185,7 +186,7 @@ func TestAccSSMPatchBaseline_operatingSystem(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "approval_rule.#", "1"), resource.TestCheckResourceAttr(resourceName, "approval_rule.0.approve_after_days", "7"), resource.TestCheckResourceAttr(resourceName, "approval_rule.0.patch_filter.#", "2"), - resource.TestCheckResourceAttr(resourceName, "approval_rule.0.compliance_level", ssm.PatchComplianceLevelCritical), + resource.TestCheckResourceAttr(resourceName, "approval_rule.0.compliance_level", string(awstypes.PatchComplianceLevelCritical)), resource.TestCheckResourceAttr(resourceName, "approval_rule.0.enable_non_security", "true"), resource.TestCheckResourceAttr(resourceName, "operating_system", "AMAZON_LINUX"), ), @@ -202,8 +203,8 @@ func TestAccSSMPatchBaseline_operatingSystem(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "approval_rule.#", "1"), resource.TestCheckResourceAttr(resourceName, "approval_rule.0.approve_after_days", "7"), resource.TestCheckResourceAttr(resourceName, "approval_rule.0.patch_filter.#", "2"), - resource.TestCheckResourceAttr(resourceName, "approval_rule.0.compliance_level", ssm.PatchComplianceLevelInformational), - resource.TestCheckResourceAttr(resourceName, "operating_system", ssm.OperatingSystemWindows), + resource.TestCheckResourceAttr(resourceName, "approval_rule.0.compliance_level", string(awstypes.PatchComplianceLevelInformational)), + resource.TestCheckResourceAttr(resourceName, "operating_system", string(awstypes.OperatingSystemWindows)), testAccCheckPatchBaselineRecreated(t, &before, &after), ), }, @@ -230,7 +231,7 @@ func TestAccSSMPatchBaseline_approveUntilDateParam(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "approval_rule.#", "1"), resource.TestCheckResourceAttr(resourceName, "approval_rule.0.approve_until_date", "2020-01-01"), resource.TestCheckResourceAttr(resourceName, "approval_rule.0.patch_filter.#", "2"), - resource.TestCheckResourceAttr(resourceName, "approval_rule.0.compliance_level", ssm.PatchComplianceLevelCritical), + resource.TestCheckResourceAttr(resourceName, "approval_rule.0.compliance_level", string(awstypes.PatchComplianceLevelCritical)), resource.TestCheckResourceAttr(resourceName, "approval_rule.0.enable_non_security", "true"), resource.TestCheckResourceAttr(resourceName, "operating_system", "AMAZON_LINUX"), ), @@ -247,10 +248,10 @@ func TestAccSSMPatchBaseline_approveUntilDateParam(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "approval_rule.#", "1"), resource.TestCheckResourceAttr(resourceName, "approval_rule.0.approve_until_date", "2020-02-02"), resource.TestCheckResourceAttr(resourceName, "approval_rule.0.patch_filter.#", "2"), - resource.TestCheckResourceAttr(resourceName, "approval_rule.0.compliance_level", ssm.PatchComplianceLevelCritical), + resource.TestCheckResourceAttr(resourceName, "approval_rule.0.compliance_level", string(awstypes.PatchComplianceLevelCritical)), resource.TestCheckResourceAttr(resourceName, "operating_system", "AMAZON_LINUX"), func(*terraform.State) error { - if aws.StringValue(before.BaselineId) != aws.StringValue(after.BaselineId) { + if aws.ToString(before.BaselineId) != aws.ToString(after.BaselineId) { t.Fatal("Baseline IDs changed unexpectedly") } return nil @@ -304,7 +305,7 @@ func TestAccSSMPatchBaseline_sources(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "source.1.products.0", "AmazonLinux2018.03"), func(*terraform.State) error { - if aws.StringValue(before.BaselineId) != aws.StringValue(after.BaselineId) { + if aws.ToString(before.BaselineId) != aws.ToString(after.BaselineId) { t.Fatal("Baseline IDs changed unexpectedly") } return nil @@ -393,13 +394,13 @@ func testAccSSMPatchBaseline_deleteDefault(t *testing.T) { }, { PreConfig: func() { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) input := &ssm.RegisterDefaultPatchBaselineInput{ BaselineId: ssmPatch.BaselineId, } - if _, err := conn.RegisterDefaultPatchBaselineWithContext(ctx, input); err != nil { - t.Fatalf("registering Default Patch Baseline (%s): %s", aws.StringValue(ssmPatch.BaselineId), err) + if _, err := conn.RegisterDefaultPatchBaseline(ctx, input); err != nil { + t.Fatalf("registering Default Patch Baseline (%s): %s", aws.ToString(ssmPatch.BaselineId), err) } }, Config: "# Empty config", // Deletes the patch baseline @@ -425,7 +426,7 @@ func testAccCheckPatchBaselineExists(ctx context.Context, n string, v *ssm.GetPa return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) output, err := tfssm.FindPatchBaselineByID(ctx, conn, rs.Primary.ID) @@ -441,7 +442,7 @@ func testAccCheckPatchBaselineExists(ctx context.Context, n string, v *ssm.GetPa func testAccCheckPatchBaselineDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ssm_patch_baseline" { @@ -468,7 +469,7 @@ func testAccCheckPatchBaselineDestroy(ctx context.Context) resource.TestCheckFun func testAccPatchBaselineConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_ssm_patch_baseline" "test" { - name = "patch-baseline-%s" + name = "patch-baseline-%[1]s" description = "Baseline containing all updates approved for production systems" approved_patches = ["KB123456"] approved_patches_compliance_level = "CRITICAL" @@ -476,7 +477,7 @@ resource "aws_ssm_patch_baseline" "test" { `, rName) } -func testAccPatchBaselineConfig_basicTags1(rName, tagKey1, tagValue1 string) string { +func testAccPatchBaselineConfig_tags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_ssm_patch_baseline" "test" { name = %[1]q @@ -491,7 +492,7 @@ resource "aws_ssm_patch_baseline" "test" { `, rName, tagKey1, tagValue1) } -func testAccPatchBaselineConfig_basicTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { +func testAccPatchBaselineConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_ssm_patch_baseline" "test" { name = %[1]q @@ -510,7 +511,7 @@ resource "aws_ssm_patch_baseline" "test" { func testAccPatchBaselineConfig_basicUpdated(rName string) string { return fmt.Sprintf(` resource "aws_ssm_patch_baseline" "test" { - name = "updated-patch-baseline-%s" + name = "updated-patch-baseline-%[1]s" description = "Baseline containing all updates approved for production systems - August 2017" approved_patches = ["KB123456", "KB456789"] approved_patches_compliance_level = "HIGH" @@ -521,7 +522,7 @@ resource "aws_ssm_patch_baseline" "test" { func testAccPatchBaselineConfig_operatingSystem(rName string) string { return fmt.Sprintf(` resource "aws_ssm_patch_baseline" "test" { - name = "patch-baseline-%s" + name = "patch-baseline-%[1]s" operating_system = "AMAZON_LINUX" description = "Baseline containing all updates approved for production systems" @@ -551,7 +552,7 @@ resource "aws_ssm_patch_baseline" "test" { func testAccPatchBaselineConfig_operatingSystemUpdated(rName string) string { return fmt.Sprintf(` resource "aws_ssm_patch_baseline" "test" { - name = "patch-baseline-%s" + name = "patch-baseline-%[1]s" operating_system = "WINDOWS" description = "Baseline containing all updates approved for production systems" @@ -682,7 +683,7 @@ resource "aws_ssm_patch_baseline" "test" { func testAccPatchBaselineConfig_basicApprovedPatchesNonSec(rName string) string { return fmt.Sprintf(` resource "aws_ssm_patch_baseline" "test" { - name = %q + name = %[1]q operating_system = "AMAZON_LINUX" description = "Baseline containing all updates approved for production systems" approved_patches = ["KB123456"] @@ -695,7 +696,7 @@ resource "aws_ssm_patch_baseline" "test" { func testAccPatchBaselineConfig_basicRejectPatchesAction(rName string) string { return fmt.Sprintf(` resource "aws_ssm_patch_baseline" "test" { - name = "patch-baseline-%s" + name = "patch-baseline-%[1]s" description = "Baseline containing all updates approved for production systems" approved_patches = ["KB123456"] approved_patches_compliance_level = "CRITICAL" From cb95f5ade743468300ebab2fd5ea42574475ae06 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 14:28:41 -0400 Subject: [PATCH 18/34] d/aws_ssm_patch_baseline: Migrate to AWS SDK for Go v2. --- .../service/ssm/patch_baseline_data_source.go | 114 +++++++++--------- internal/service/ssm/service_package_gen.go | 3 +- 2 files changed, 58 insertions(+), 59 deletions(-) diff --git a/internal/service/ssm/patch_baseline_data_source.go b/internal/service/ssm/patch_baseline_data_source.go index 9958ab94182..a460fbf5265 100644 --- a/internal/service/ssm/patch_baseline_data_source.go +++ b/internal/service/ssm/patch_baseline_data_source.go @@ -6,22 +6,25 @@ package ssm import ( "context" "encoding/json" - "log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "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/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKDataSource("aws_ssm_patch_baseline") -func DataSourcePatchBaseline() *schema.Resource { +// @SDKDataSource("aws_ssm_patch_baseline", name="Patch Baseline") +func dataSourcePatchBaseline() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataPatchBaselineRead, + Schema: map[string]*schema.Schema{ "approved_patches": { Type: schema.TypeList, @@ -116,9 +119,9 @@ func DataSourcePatchBaseline() *schema.Resource { ValidateFunc: validation.StringLenBetween(0, 255), }, "operating_system": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(ssm.OperatingSystem_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[awstypes.OperatingSystem](), }, "owner": { Type: schema.TypeString, @@ -161,97 +164,92 @@ func DataSourcePatchBaseline() *schema.Resource { func dataPatchBaselineRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - filters := []*ssm.PatchOrchestratorFilter{ + filters := []awstypes.PatchOrchestratorFilter{ { - Key: aws.String("OWNER"), - Values: []*string{ - aws.String(d.Get("owner").(string)), - }, + Key: aws.String("OWNER"), + Values: []string{d.Get("owner").(string)}, }, } if v, ok := d.GetOk(names.AttrNamePrefix); ok { - filters = append(filters, &ssm.PatchOrchestratorFilter{ - Key: aws.String("NAME_PREFIX"), - Values: []*string{ - aws.String(v.(string)), - }, + filters = append(filters, awstypes.PatchOrchestratorFilter{ + Key: aws.String("NAME_PREFIX"), + Values: []string{v.(string)}, }) } - params := &ssm.DescribePatchBaselinesInput{ + input := &ssm.DescribePatchBaselinesInput{ Filters: filters, } + var baselines []awstypes.PatchBaselineIdentity - log.Printf("[DEBUG] Reading DescribePatchBaselines: %s", params) + pages := ssm.NewDescribePatchBaselinesPaginator(conn, input) +Baselines: + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) - resp, err := conn.DescribePatchBaselinesWithContext(ctx, params) - - if err != nil { - return sdkdiag.AppendErrorf(diags, "describing SSM PatchBaselines: %s", err) - } + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading SSM Patch Baselines: %s", err) + } - var filteredBaselines []*ssm.PatchBaselineIdentity - if v, ok := d.GetOk("operating_system"); ok { - for _, baseline := range resp.BaselineIdentities { - if v.(string) == aws.StringValue(baseline.OperatingSystem) { - filteredBaselines = append(filteredBaselines, baseline) + for _, baseline := range page.BaselineIdentities { + if v, ok := d.GetOk("operating_system"); ok { + if awstypes.OperatingSystem(v.(string)) == baseline.OperatingSystem { + baselines = append(baselines, baseline) + } } - } - } - if v, ok := d.GetOk("default_baseline"); ok { - for _, baseline := range filteredBaselines { - if v.(bool) == aws.BoolValue(baseline.DefaultBaseline) { - filteredBaselines = []*ssm.PatchBaselineIdentity{baseline} - break + if v, ok := d.GetOk("default_baseline"); ok { + if v.(bool) == baseline.DefaultBaseline { + baselines = []awstypes.PatchBaselineIdentity{baseline} + break Baselines + } } } } - if len(filteredBaselines) < 1 || filteredBaselines[0] == nil { - return sdkdiag.AppendErrorf(diags, "Your query returned no results. Please change your search criteria and try again.") - } - - if len(filteredBaselines) > 1 { - return sdkdiag.AppendErrorf(diags, "Your query returned more than one result. Please try a more specific search criteria") - } + baseline, err := tfresource.AssertSingleValueResult(baselines) - baseline := filteredBaselines[0] + if err != nil { + return sdkdiag.AppendFromErr(diags, tfresource.SingularDataSourceFindError("SSM Patch Baseline", err)) - input := &ssm.GetPatchBaselineInput{ - BaselineId: baseline.BaselineId, } - output, err := conn.GetPatchBaselineWithContext(ctx, input) + id := aws.ToString(baseline.BaselineId) + output, err := findPatchBaselineByID(ctx, conn, id) if err != nil { - return sdkdiag.AppendErrorf(diags, "getting SSM PatchBaseline: %s", err) + return sdkdiag.AppendErrorf(diags, "reading SSM Patch Baseline (%s): %s", id, err) } jsonDoc, err := json.MarshalIndent(output, "", " ") if err != nil { - // should never happen if the above code is correct - return sdkdiag.AppendErrorf(diags, "Formatting json representation: formatting JSON: %s", err) + return sdkdiag.AppendFromErr(diags, err) } jsonString := string(jsonDoc) - d.SetId(aws.StringValue(baseline.BaselineId)) - d.Set("approved_patches", aws.StringValueSlice(output.ApprovedPatches)) + d.SetId(id) + d.Set("approved_patches", output.ApprovedPatches) d.Set("approved_patches_compliance_level", output.ApprovedPatchesComplianceLevel) d.Set("approved_patches_enable_non_security", output.ApprovedPatchesEnableNonSecurity) - d.Set("approval_rule", flattenPatchRuleGroup(output.ApprovalRules)) + if err := d.Set("approval_rule", flattenPatchRuleGroup(output.ApprovalRules)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting approval_rule: %s", err) + } d.Set("default_baseline", baseline.DefaultBaseline) d.Set(names.AttrDescription, baseline.BaselineDescription) - d.Set("global_filter", flattenPatchFilterGroup(output.GlobalFilters)) + if err := d.Set("global_filter", flattenPatchFilterGroup(output.GlobalFilters)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting global_filter: %s", err) + } d.Set("json", jsonString) d.Set(names.AttrName, baseline.BaselineName) d.Set("operating_system", baseline.OperatingSystem) - d.Set("rejected_patches", aws.StringValueSlice(output.RejectedPatches)) + d.Set("rejected_patches", output.RejectedPatches) d.Set("rejected_patches_action", output.RejectedPatchesAction) - d.Set(names.AttrSource, flattenPatchSource(output.Sources)) + if err := d.Set(names.AttrSource, flattenPatchSource(output.Sources)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting source: %s", err) + } return diags } diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index 2c144e78b9a..a6356c0ec86 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -50,8 +50,9 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Name: "Parameters By Path", }, { - Factory: DataSourcePatchBaseline, + Factory: dataSourcePatchBaseline, TypeName: "aws_ssm_patch_baseline", + Name: "Patch Baseline", }, } } From f0ab1452c08b72e9502b4267adf3ccd5886ecdc7 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 14:40:13 -0400 Subject: [PATCH 19/34] Tidy up return 'diag.Diagnostics'. --- internal/service/codepipeline/codepipeline.go | 4 +++- internal/service/ec2/vpc_default_network_acl.go | 6 +----- internal/service/inspector2/organization_configuration.go | 5 ++++- internal/service/lightsail/flex.go | 4 +++- internal/service/ssm/patch_baseline.go | 7 ++++--- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/internal/service/codepipeline/codepipeline.go b/internal/service/codepipeline/codepipeline.go index a7fcda86ed1..11257d23983 100644 --- a/internal/service/codepipeline/codepipeline.go +++ b/internal/service/codepipeline/codepipeline.go @@ -601,7 +601,9 @@ func findPipelineByName(ctx context.Context, conn *codepipeline.Client, name str return output, nil } -func pipelineValidateActionProvider(i interface{}, path cty.Path) (diags diag.Diagnostics) { +func pipelineValidateActionProvider(i interface{}, path cty.Path) diag.Diagnostics { + var diags diag.Diagnostics + v, ok := i.(string) if !ok { return sdkdiag.AppendErrorf(diags, "expected type to be string") diff --git a/internal/service/ec2/vpc_default_network_acl.go b/internal/service/ec2/vpc_default_network_acl.go index 07365d2284c..bff50b83bcf 100644 --- a/internal/service/ec2/vpc_default_network_acl.go +++ b/internal/service/ec2/vpc_default_network_acl.go @@ -32,7 +32,7 @@ func resourceDefaultNetworkACL() *schema.Resource { CreateWithoutTimeout: resourceDefaultNetworkACLCreate, ReadWithoutTimeout: resourceNetworkACLRead, UpdateWithoutTimeout: resourceDefaultNetworkACLUpdate, - DeleteWithoutTimeout: resourceDefaultNetworkACLDelete, + DeleteWithoutTimeout: schema.NoopContext, Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { @@ -158,7 +158,3 @@ func resourceDefaultNetworkACLUpdate(ctx context.Context, d *schema.ResourceData return append(diags, resourceNetworkACLRead(ctx, d, meta)...) } - -func resourceDefaultNetworkACLDelete(_ context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) { - return sdkdiag.AppendWarningf(diags, "EC2 Default Network ACL (%s) not deleted, removing from state", d.Id()) -} diff --git a/internal/service/inspector2/organization_configuration.go b/internal/service/inspector2/organization_configuration.go index 46ff901a5ac..79639412642 100644 --- a/internal/service/inspector2/organization_configuration.go +++ b/internal/service/inspector2/organization_configuration.go @@ -77,8 +77,11 @@ const ( orgConfigMutex = "f14b54d7-2b10-58c2-9c1b-c48260a4825d" ) -func resourceOrganizationConfigurationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) { +func resourceOrganizationConfigurationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + d.SetId(meta.(*conns.AWSClient).AccountID) + return append(diags, resourceOrganizationConfigurationUpdate(ctx, d, meta)...) } diff --git a/internal/service/lightsail/flex.go b/internal/service/lightsail/flex.go index 20ffea85cea..eb5ca1b3f7c 100644 --- a/internal/service/lightsail/flex.go +++ b/internal/service/lightsail/flex.go @@ -16,7 +16,9 @@ import ( ) // expandOperations provides a uniform approach for handling lightsail operations and errors. -func expandOperations(ctx context.Context, conn *lightsail.Client, operations []types.Operation, action types.OperationType, resource string, id string) (diags diag.Diagnostics) { +func expandOperations(ctx context.Context, conn *lightsail.Client, operations []types.Operation, action types.OperationType, resource string, id string) diag.Diagnostics { + var diags diag.Diagnostics + if len(operations) == 0 { return create.AppendDiagError(diags, names.Lightsail, string(action), resource, id, errors.New("no operations found for request")) } diff --git a/internal/service/ssm/patch_baseline.go b/internal/service/ssm/patch_baseline.go index bd6731f2179..7daa5f3ae98 100644 --- a/internal/service/ssm/patch_baseline.go +++ b/internal/service/ssm/patch_baseline.go @@ -407,7 +407,8 @@ func resourcePatchBaselineUpdate(ctx context.Context, d *schema.ResourceData, me return append(diags, resourcePatchBaselineRead(ctx, d, meta)...) } -func resourcePatchBaselineDelete(ctx context.Context, d *schema.ResourceData, meta any) (diags diag.Diagnostics) { +func resourcePatchBaselineDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SSMClient(ctx) log.Printf("[INFO] Deleting SSM Patch Baseline: %s", d.Id()) @@ -421,7 +422,7 @@ func resourcePatchBaselineDelete(ctx context.Context, d *schema.ResourceData, me // Reset the default patch baseline before retrying. diags = append(diags, defaultPatchBaselineRestoreOSDefault(ctx, meta.(*conns.AWSClient).SSMClient(ctx), awstypes.OperatingSystem(d.Get("operating_system").(string)))...) if diags.HasError() { - return + return diags } _, err = conn.DeletePatchBaseline(ctx, input) @@ -431,7 +432,7 @@ func resourcePatchBaselineDelete(ctx context.Context, d *schema.ResourceData, me diags = sdkdiag.AppendErrorf(diags, "deleting SSM Patch Baseline (%s): %s", d.Id(), err) } - return + return diags } func findPatchBaselineByID(ctx context.Context, conn *ssm.Client, id string) (*ssm.GetPatchBaselineOutput, error) { From c7136e6fdcebb911a0b531fadc59dc9e0d85deaf Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 15:02:30 -0400 Subject: [PATCH 20/34] r/aws_ssm_default_patch_baseline: Tidy up. --- .../service/ssm/default_patch_baseline.go | 339 ++++++++---------- .../ssm/default_patch_baseline_test.go | 54 ++- internal/service/ssm/exports_test.go | 18 +- 3 files changed, 183 insertions(+), 228 deletions(-) diff --git a/internal/service/ssm/default_patch_baseline.go b/internal/service/ssm/default_patch_baseline.go index 4c7467ae38a..6c6a51d07d0 100644 --- a/internal/service/ssm/default_patch_baseline.go +++ b/internal/service/ssm/default_patch_baseline.go @@ -5,7 +5,6 @@ package ssm import ( "context" - "errors" "fmt" "log" "slices" @@ -14,19 +13,17 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/service/ssm" - "github.com/aws/aws-sdk-go-v2/service/ssm/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/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" tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" - "github.com/hashicorp/terraform-provider-aws/names" ) const ( @@ -47,13 +44,13 @@ func resourceDefaultPatchBaseline() *schema.Resource { if isPatchBaselineID(id) || isPatchBaselineARN(id) { conn := meta.(*conns.AWSClient).SSMClient(ctx) - patchbaseline, err := findPatchBaselineByIDV2(ctx, conn, id) + baseline, err := findPatchBaselineByID(ctx, conn, id) if err != nil { return nil, fmt.Errorf("reading SSM Patch Baseline (%s): %w", id, err) } - d.SetId(string(patchbaseline.OperatingSystem)) - } else if vals := enum.Values[types.OperatingSystem](); !slices.Contains(vals, id) { + d.SetId(string(baseline.OperatingSystem)) + } else if vals := enum.Values[awstypes.OperatingSystem](); !slices.Contains(vals, id) { return nil, fmt.Errorf("ID (%s) must be either a Patch Baseline ID, Patch Baseline ARN, or one of %v", id, vals) } @@ -72,127 +69,39 @@ func resourceDefaultPatchBaseline() *schema.Resource { validatePatchBaselineARN, ), }, - "operating_system": { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateDiagFunc: enum.Validate[types.OperatingSystem](), + ValidateDiagFunc: enum.Validate[awstypes.OperatingSystem](), }, }, } } -func diffSuppressPatchBaselineID(_, oldValue, newValue string, _ *schema.ResourceData) bool { - if oldValue == newValue { - return true - } - - oldId := oldValue - if arn.IsARN(oldValue) { - oldId = patchBaselineIDFromARN(oldValue) - } - - newId := newValue - if arn.IsARN(newValue) { - newId = patchBaselineIDFromARN(newValue) - } - - if oldId == newId { - return true - } - - return false -} - -var validatePatchBaselineID = validation.StringMatch(regexache.MustCompile(`^`+patchBaselineIDRegexPattern+`$`), `must match "pb-" followed by 17 hexadecimal characters`) - -func validatePatchBaselineARN(v any, k string) (ws []string, errors []error) { - value, ok := v.(string) - if !ok { - errors = append(errors, fmt.Errorf("expected type of %s to be string", k)) - return - } - - if value == "" { - return - } - - if _, err := arn.Parse(value); err != nil { - errors = append(errors, fmt.Errorf("%q (%s) is not a valid ARN: %s", k, value, err)) - return - } - - if !isPatchBaselineARN(value) { - errors = append(errors, fmt.Errorf("%q (%s) is not a valid SSM Patch Baseline ARN", k, value)) - return - } - - return -} - -func isPatchBaselineID(s string) bool { - re := regexache.MustCompile(`^` + patchBaselineIDRegexPattern + `$`) - - return re.MatchString(s) -} - -func isPatchBaselineARN(s string) bool { - parsedARN, err := arn.Parse(s) - if err != nil { - return false - } - - return patchBaselineIDFromARNResource(parsedARN.Resource) != "" -} - -func patchBaselineIDFromARN(s string) string { - arn, err := arn.Parse(s) - if err != nil { - return "" - } - - return patchBaselineIDFromARNResource(arn.Resource) -} - -func patchBaselineIDFromARNResource(s string) string { - re := regexache.MustCompile(`^patchbaseline/(` + patchBaselineIDRegexPattern + ")$") - matches := re.FindStringSubmatch(s) - if matches == nil || len(matches) != 2 { - return "" - } - - return matches[1] -} - -const ( - ResNameDefaultPatchBaseline = "Default Patch Baseline" -) - func resourceDefaultPatchBaselineCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SSMClient(ctx) baselineID := d.Get("baseline_id").(string) - - patchBaseline, err := findPatchBaselineByIDV2(ctx, conn, baselineID) + patchBaseline, err := findPatchBaselineByID(ctx, conn, baselineID) if err != nil { return sdkdiag.AppendErrorf(diags, "reading SSM Patch Baseline (%s): %s", baselineID, err) } - if pbOS, cOS := string(patchBaseline.OperatingSystem), d.Get("operating_system"); pbOS != cOS { - return create.AppendDiagErrorMessage(diags, names.SSM, "registering", ResNameDefaultPatchBaseline, baselineID, - fmt.Sprintf("Patch Baseline Operating System (%s) does not match %s", pbOS, cOS), - ) + if pbOS, cOS := patchBaseline.OperatingSystem, awstypes.OperatingSystem(d.Get("operating_system").(string)); pbOS != cOS { + return sdkdiag.AppendErrorf(diags, "Patch Baseline Operating System (%s) does not match %s", pbOS, cOS) } - in := &ssm.RegisterDefaultPatchBaselineInput{ + input := &ssm.RegisterDefaultPatchBaselineInput{ BaselineId: aws.String(baselineID), } - _, err = conn.RegisterDefaultPatchBaseline(ctx, in) + + _, err = conn.RegisterDefaultPatchBaseline(ctx, input) + if err != nil { - return create.AppendDiagError(diags, names.SSM, "registering", ResNameDefaultPatchBaseline, baselineID, err) + return sdkdiag.AppendErrorf(diags, "registering SSM Default Patch Baseline (%s): %s", baselineID, err) } d.SetId(string(patchBaseline.OperatingSystem)) @@ -202,115 +111,60 @@ func resourceDefaultPatchBaselineCreate(ctx context.Context, d *schema.ResourceD func resourceDefaultPatchBaselineRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMClient(ctx) - out, err := FindDefaultPatchBaseline(ctx, conn, types.OperatingSystem(d.Id())) + output, err := findDefaultPatchBaselineByOperatingSystem(ctx, conn, awstypes.OperatingSystem(d.Id())) + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] SSM Default Patch Baseline (%s) not found, removing from state", d.Id()) d.SetId("") return nil } + if err != nil { - return create.AppendDiagError(diags, names.SSM, create.ErrActionReading, ResNameDefaultPatchBaseline, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading SSM Default Patch Baseline (%s): %s", d.Id(), err) } - d.Set("baseline_id", out.BaselineId) - d.Set("operating_system", out.OperatingSystem) + d.Set("baseline_id", output.BaselineId) + d.Set("operating_system", output.OperatingSystem) return diags } -func operatingSystemFilter(os ...types.OperatingSystem) types.PatchOrchestratorFilter { - return types.PatchOrchestratorFilter{ - Key: aws.String("OPERATING_SYSTEM"), - Values: toStringSlice(os), - } -} - -func toStringSlice[T ~string](os []T) []string { - return tfslices.ApplyToAll(os, func(t T) string { - return string(t) - }) -} - -func ownerIsAWSFilter() types.PatchOrchestratorFilter { // nosemgrep:ci.aws-in-func-name - return types.PatchOrchestratorFilter{ - Key: aws.String("OWNER"), - Values: []string{"AWS"}, - } -} - -func ownerIsSelfFilter() types.PatchOrchestratorFilter { - return types.PatchOrchestratorFilter{ - Key: aws.String("OWNER"), - Values: []string{"Self"}, - } -} - -func resourceDefaultPatchBaselineDelete(ctx context.Context, d *schema.ResourceData, meta any) (diags diag.Diagnostics) { - return defaultPatchBaselineRestoreOSDefault(ctx, meta.(*conns.AWSClient).SSMClient(ctx), types.OperatingSystem(d.Id())) +func resourceDefaultPatchBaselineDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + return defaultPatchBaselineRestoreOSDefault(ctx, meta.(*conns.AWSClient).SSMClient(ctx), awstypes.OperatingSystem(d.Id())) } -func defaultPatchBaselineRestoreOSDefault(ctx context.Context, conn *ssm.Client, os types.OperatingSystem) (diags diag.Diagnostics) { - baselineID, err := FindDefaultDefaultPatchBaselineIDForOS(ctx, conn, os) - if errors.Is(err, tfresource.ErrEmptyResult) { - diags = sdkdiag.AppendWarningf(diags, "no AWS-owned default Patch Baseline found for operating system %q", os) - return - } - var tmr *tfresource.TooManyResultsError - if errors.As(err, &tmr) { - diags = sdkdiag.AppendWarningf(diags, "found %d AWS-owned default Patch Baselines found for operating system %q", tmr.Count, os) - } - if err != nil { - diags = sdkdiag.AppendErrorf(diags, "finding AWS-owned default Patch Baseline for operating system %q: %s", os, err) - } +func defaultPatchBaselineRestoreOSDefault(ctx context.Context, conn *ssm.Client, os awstypes.OperatingSystem) diag.Diagnostics { + var diags diag.Diagnostics - log.Printf("[INFO] Restoring SSM Default Patch Baseline for operating system %q to %q", os, baselineID) + baselineID, err := findDefaultDefaultPatchBaselineIDByOperatingSystem(ctx, conn, os) - in := &ssm.RegisterDefaultPatchBaselineInput{ - BaselineId: aws.String(baselineID), - } - _, err = conn.RegisterDefaultPatchBaseline(ctx, in) if err != nil { - diags = sdkdiag.AppendErrorf(diags, "restoring SSM Default Patch Baseline for operating system %q to %q: %s", os, baselineID, err) + return sdkdiag.AppendErrorf(diags, "reading AWS-owned SSM Default Patch Baseline for operating system (%s): %s", os, err) } - return -} - -func FindDefaultPatchBaseline(ctx context.Context, conn *ssm.Client, os types.OperatingSystem) (*ssm.GetDefaultPatchBaselineOutput, error) { - in := &ssm.GetDefaultPatchBaselineInput{ - OperatingSystem: os, + input := &ssm.RegisterDefaultPatchBaselineInput{ + BaselineId: baselineID, } - out, err := conn.GetDefaultPatchBaseline(ctx, in) - if errs.IsA[*types.DoesNotExistException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } + _, err = conn.RegisterDefaultPatchBaseline(ctx, input) if err != nil { - return nil, err - } - - if out == nil { - return nil, tfresource.NewEmptyResultError(in) + return sdkdiag.AppendErrorf(diags, "restoring SSM Default Patch Baseline for operating system (%s) to (%s): %s", os, baselineID, err) } - return out, nil + return diags } -func findPatchBaselineByIDV2(ctx context.Context, conn *ssm.Client, id string) (*ssm.GetPatchBaselineOutput, error) { - input := &ssm.GetPatchBaselineInput{ - BaselineId: aws.String(id), +func findDefaultPatchBaselineByOperatingSystem(ctx context.Context, conn *ssm.Client, os awstypes.OperatingSystem) (*ssm.GetDefaultPatchBaselineOutput, error) { + input := &ssm.GetDefaultPatchBaselineInput{ + OperatingSystem: os, } - output, err := conn.GetPatchBaseline(ctx, input) + output, err := conn.GetDefaultPatchBaseline(ctx, input) - if errs.IsA[*types.DoesNotExistException](err) { + if errs.IsA[*awstypes.DoesNotExistException](err) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, @@ -328,37 +182,138 @@ func findPatchBaselineByIDV2(ctx context.Context, conn *ssm.Client, id string) ( return output, nil } -func patchBaselinesPaginator(conn *ssm.Client, filters ...types.PatchOrchestratorFilter) *ssm.DescribePatchBaselinesPaginator { +func patchBaselinesPaginator(conn *ssm.Client, filters ...awstypes.PatchOrchestratorFilter) *ssm.DescribePatchBaselinesPaginator { return ssm.NewDescribePatchBaselinesPaginator(conn, &ssm.DescribePatchBaselinesInput{ Filters: filters, }) } -func FindDefaultDefaultPatchBaselineIDForOS(ctx context.Context, conn *ssm.Client, os types.OperatingSystem) (string, error) { +func findDefaultDefaultPatchBaselineIDByOperatingSystem(ctx context.Context, conn *ssm.Client, os awstypes.OperatingSystem) (*string, error) { paginator := patchBaselinesPaginator(conn, operatingSystemFilter(os), ownerIsAWSFilter(), ) re := regexache.MustCompile(`^AWS-[0-9A-Za-z]+PatchBaseline$`) var baselineIdentityIDs []string + for paginator.HasMorePages() { page, err := paginator.NextPage(ctx) + if err != nil { - return "", fmt.Errorf("listing Patch Baselines for operating system %q: %s", os, err) + return nil, fmt.Errorf("reading SSM Patch Baselines: %s", err) } - for _, identity := range page.BaselineIdentities { - if id := aws.ToString(identity.BaselineName); re.MatchString(id) { - baselineIdentityIDs = append(baselineIdentityIDs, aws.ToString(identity.BaselineId)) + for _, v := range page.BaselineIdentities { + if id := aws.ToString(v.BaselineName); re.MatchString(id) { + baselineIdentityIDs = append(baselineIdentityIDs, aws.ToString(v.BaselineId)) } } } - if l := len(baselineIdentityIDs); l == 0 { - return "", tfresource.NewEmptyResultError(nil) - } else if l > 1 { - return "", tfresource.NewTooManyResultsError(l, nil) + return tfresource.AssertSingleValueResult(baselineIdentityIDs) +} + +func operatingSystemFilter(os ...awstypes.OperatingSystem) awstypes.PatchOrchestratorFilter { + return awstypes.PatchOrchestratorFilter{ + Key: aws.String("OPERATING_SYSTEM"), + Values: tfslices.ApplyToAll(os, func(v awstypes.OperatingSystem) string { + return string(v) + }), + } +} + +func ownerIsAWSFilter() awstypes.PatchOrchestratorFilter { // nosemgrep:ci.aws-in-func-name + return awstypes.PatchOrchestratorFilter{ + Key: aws.String("OWNER"), + Values: []string{"AWS"}, + } +} + +func ownerIsSelfFilter() awstypes.PatchOrchestratorFilter { + return awstypes.PatchOrchestratorFilter{ + Key: aws.String("OWNER"), + Values: []string{"Self"}, + } +} + +func diffSuppressPatchBaselineID(_, oldValue, newValue string, _ *schema.ResourceData) bool { + if oldValue == newValue { + return true + } + + oldId := oldValue + if arn.IsARN(oldValue) { + oldId = patchBaselineIDFromARN(oldValue) + } + + newId := newValue + if arn.IsARN(newValue) { + newId = patchBaselineIDFromARN(newValue) + } + + if oldId == newId { + return true + } + + return false +} + +var validatePatchBaselineID = validation.StringMatch(regexache.MustCompile(`^`+patchBaselineIDRegexPattern+`$`), `must match "pb-" followed by 17 hexadecimal characters`) + +func validatePatchBaselineARN(v any, k string) (ws []string, errors []error) { + value, ok := v.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %s to be string", k)) + return + } + + if value == "" { + return + } + + if _, err := arn.Parse(value); err != nil { + errors = append(errors, fmt.Errorf("%q (%s) is not a valid ARN: %s", k, value, err)) + return + } + + if !isPatchBaselineARN(value) { + errors = append(errors, fmt.Errorf("%q (%s) is not a valid SSM Patch Baseline ARN", k, value)) + return + } + + return +} + +func isPatchBaselineID(s string) bool { + re := regexache.MustCompile(`^` + patchBaselineIDRegexPattern + `$`) + + return re.MatchString(s) +} + +func isPatchBaselineARN(s string) bool { + parsedARN, err := arn.Parse(s) + if err != nil { + return false + } + + return patchBaselineIDFromARNResource(parsedARN.Resource) != "" +} + +func patchBaselineIDFromARN(s string) string { + arn, err := arn.Parse(s) + if err != nil { + return "" + } + + return patchBaselineIDFromARNResource(arn.Resource) +} + +func patchBaselineIDFromARNResource(s string) string { + re := regexache.MustCompile(`^patchbaseline/(` + patchBaselineIDRegexPattern + ")$") + matches := re.FindStringSubmatch(s) + if matches == nil || len(matches) != 2 { + return "" } - return baselineIdentityIDs[0], nil + return matches[1] } diff --git a/internal/service/ssm/default_patch_baseline_test.go b/internal/service/ssm/default_patch_baseline_test.go index ac7a5124cd6..41853e33caa 100644 --- a/internal/service/ssm/default_patch_baseline_test.go +++ b/internal/service/ssm/default_patch_baseline_test.go @@ -5,7 +5,6 @@ package ssm_test import ( "context" - "errors" "fmt" "regexp" "testing" @@ -13,13 +12,12 @@ import ( "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssm" - "github.com/aws/aws-sdk-go-v2/service/ssm/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" 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" - "github.com/hashicorp/terraform-provider-aws/internal/create" tfssm "github.com/hashicorp/terraform-provider-aws/internal/service/ssm" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" @@ -151,7 +149,7 @@ func testAccSSMDefaultPatchBaseline_otherOperatingSystem(t *testing.T) { CheckDestroy: testAccCheckDefaultPatchBaselineDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccDefaultPatchBaselineConfig_operatingSystem(rName, types.OperatingSystemAmazonLinux2022), + Config: testAccDefaultPatchBaselineConfig_operatingSystem(rName, awstypes.OperatingSystemAmazonLinux2022), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckDefaultPatchBaselineExists(ctx, resourceName, &defaultpatchbaseline), resource.TestCheckResourceAttrPair(resourceName, "baseline_id", baselineResourceName, names.AttrID), @@ -189,8 +187,8 @@ func testAccSSMDefaultPatchBaseline_wrongOperatingSystem(t *testing.T) { CheckDestroy: testAccCheckDefaultPatchBaselineDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccDefaultPatchBaselineConfig_wrongOperatingSystem(rName, types.OperatingSystemAmazonLinux2022, types.OperatingSystemUbuntu), - ExpectError: regexache.MustCompile(regexp.QuoteMeta(fmt.Sprintf("Patch Baseline Operating System (%s) does not match %s", types.OperatingSystemAmazonLinux2022, types.OperatingSystemUbuntu))), + Config: testAccDefaultPatchBaselineConfig_wrongOperatingSystem(rName, awstypes.OperatingSystemAmazonLinux2022, awstypes.OperatingSystemUbuntu), + ExpectError: regexache.MustCompile(regexp.QuoteMeta(fmt.Sprintf("Patch Baseline Operating System (%s) does not match %s", awstypes.OperatingSystemAmazonLinux2022, awstypes.OperatingSystemUbuntu))), }, }, }) @@ -254,7 +252,7 @@ func testAccSSMDefaultPatchBaseline_update(t *testing.T) { CheckDestroy: testAccCheckDefaultPatchBaselineDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccDefaultPatchBaselineConfig_operatingSystem(rName, types.OperatingSystemWindows), + Config: testAccDefaultPatchBaselineConfig_operatingSystem(rName, awstypes.OperatingSystemWindows), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckDefaultPatchBaselineExists(ctx, resourceName, &v1), resource.TestCheckResourceAttrPair(resourceName, "baseline_id", baselineResourceName, names.AttrID), @@ -262,7 +260,7 @@ func testAccSSMDefaultPatchBaseline_update(t *testing.T) { ), }, { - Config: testAccDefaultPatchBaselineConfig_updated(rName, types.OperatingSystemWindows), + Config: testAccDefaultPatchBaselineConfig_updated(rName, awstypes.OperatingSystemWindows), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckDefaultPatchBaselineExists(ctx, resourceName, &v2), resource.TestCheckResourceAttrPair(resourceName, "baseline_id", baselineUpdatedResourceName, names.AttrID), @@ -343,50 +341,50 @@ func testAccCheckDefaultPatchBaselineDestroy(ctx context.Context) resource.TestC continue } - defaultOSPatchBaseline, err := tfssm.FindDefaultDefaultPatchBaselineIDForOS(ctx, conn, types.OperatingSystem(rs.Primary.ID)) + defaultOSPatchBaseline, err := tfssm.FindDefaultDefaultPatchBaselineIDByOperatingSystem(ctx, conn, awstypes.OperatingSystem(rs.Primary.ID)) + if err != nil { return err } - // If the resource has been deleted, the default patch baseline will be the AWS-provided patch baseline for the OS - out, err := tfssm.FindDefaultPatchBaseline(ctx, conn, types.OperatingSystem(rs.Primary.ID)) + // If the resource has been deleted, the default patch baseline will be the AWS-provided patch baseline for the OS. + output, err := tfssm.FindDefaultPatchBaselineByOperatingSystem(ctx, conn, awstypes.OperatingSystem(rs.Primary.ID)) + if tfresource.NotFound(err) { - return nil + continue } + if err != nil { return err } - if aws.ToString(out.BaselineId) == defaultOSPatchBaseline { - return nil + if aws.ToString(output.BaselineId) == aws.ToString(defaultOSPatchBaseline) { + continue } - return create.Error(names.SSM, create.ErrActionCheckingDestroyed, tfssm.ResNameDefaultPatchBaseline, rs.Primary.ID, errors.New("not destroyed")) + return fmt.Errorf("SSM Default Patch Baseline %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckDefaultPatchBaselineExists(ctx context.Context, name string, defaultpatchbaseline *ssm.GetDefaultPatchBaselineOutput) resource.TestCheckFunc { +func testAccCheckDefaultPatchBaselineExists(ctx context.Context, n string, v *ssm.GetDefaultPatchBaselineOutput) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.SSM, create.ErrActionCheckingExistence, tfssm.ResNameDefaultPatchBaseline, name, errors.New("not found")) - } - - if rs.Primary.ID == "" { - return create.Error(names.SSM, create.ErrActionCheckingExistence, tfssm.ResNameDefaultPatchBaseline, name, errors.New("not set")) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) - resp, err := tfssm.FindDefaultPatchBaseline(ctx, conn, types.OperatingSystem(rs.Primary.ID)) + output, err := tfssm.FindDefaultPatchBaselineByOperatingSystem(ctx, conn, awstypes.OperatingSystem(rs.Primary.ID)) + if err != nil { - return create.Error(names.SSM, create.ErrActionCheckingExistence, tfssm.ResNameDefaultPatchBaseline, rs.Primary.ID, err) + return err } - *defaultpatchbaseline = *resp + *v = *output return nil } @@ -419,7 +417,7 @@ resource "aws_ssm_patch_baseline" "test" { `, rName) } -func testAccDefaultPatchBaselineConfig_operatingSystem(rName string, os types.OperatingSystem) string { +func testAccDefaultPatchBaselineConfig_operatingSystem(rName string, os awstypes.OperatingSystem) string { return fmt.Sprintf(` resource "aws_ssm_default_patch_baseline" "test" { baseline_id = aws_ssm_patch_baseline.test.id @@ -436,7 +434,7 @@ resource "aws_ssm_patch_baseline" "test" { `, rName, os) } -func testAccDefaultPatchBaselineConfig_wrongOperatingSystem(rName string, baselineOS, defaultOS types.OperatingSystem) string { +func testAccDefaultPatchBaselineConfig_wrongOperatingSystem(rName string, baselineOS, defaultOS awstypes.OperatingSystem) string { return fmt.Sprintf(` resource "aws_ssm_default_patch_baseline" "test" { baseline_id = aws_ssm_patch_baseline.test.id @@ -484,7 +482,7 @@ data "aws_ssm_patch_baseline" "test" { ` } -func testAccDefaultPatchBaselineConfig_updated(rName string, os types.OperatingSystem) string { +func testAccDefaultPatchBaselineConfig_updated(rName string, os awstypes.OperatingSystem) string { return fmt.Sprintf(` resource "aws_ssm_default_patch_baseline" "test" { baseline_id = aws_ssm_patch_baseline.updated.id diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index 342c971870a..f74b805b5ee 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -15,12 +15,14 @@ var ( ResourceParameter = resourceParameter ResourcePatchBaseline = resourcePatchBaseline - FindActivationByID = findActivationByID - FindAssociationByID = findAssociationByID - FindDocumentByName = findDocumentByName - FindMaintenanceWindowByID = findMaintenanceWindowByID - FindMaintenanceWindowTargetByID = findMaintenanceWindowTargetByID - FindMaintenanceWindowTaskByTwoPartKey = findMaintenanceWindowTaskByTwoPartKey - FindParameterByName = findParameterByName - FindPatchBaselineByID = findPatchBaselineByID + FindActivationByID = findActivationByID + FindAssociationByID = findAssociationByID + FindDefaultPatchBaselineByOperatingSystem = findDefaultPatchBaselineByOperatingSystem + FindDefaultDefaultPatchBaselineIDByOperatingSystem = findDefaultDefaultPatchBaselineIDByOperatingSystem + FindDocumentByName = findDocumentByName + FindMaintenanceWindowByID = findMaintenanceWindowByID + FindMaintenanceWindowTargetByID = findMaintenanceWindowTargetByID + FindMaintenanceWindowTaskByTwoPartKey = findMaintenanceWindowTaskByTwoPartKey + FindParameterByName = findParameterByName + FindPatchBaselineByID = findPatchBaselineByID ) From fadf3396c42156767ef43b8493dbd35a4c6588f6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 15:21:18 -0400 Subject: [PATCH 21/34] r/aws_ssm_patch_group: Migrate to AWS SDK for Go v2. --- internal/service/ssm/exports_test.go | 2 + internal/service/ssm/find.go | 29 ---- internal/service/ssm/patch_group.go | 130 ++++++++++++------ internal/service/ssm/patch_group_migrate.go | 2 +- .../service/ssm/patch_group_migrate_test.go | 9 +- internal/service/ssm/patch_group_test.go | 41 ++---- internal/service/ssm/service_package_gen.go | 3 +- 7 files changed, 105 insertions(+), 111 deletions(-) diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index f74b805b5ee..d0ae18c1eb8 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -14,6 +14,7 @@ var ( ResourceMaintenanceWindowTask = resourceMaintenanceWindowTask ResourceParameter = resourceParameter ResourcePatchBaseline = resourcePatchBaseline + ResourcePatchGroup = resourcePatchGroup FindActivationByID = findActivationByID FindAssociationByID = findAssociationByID @@ -25,4 +26,5 @@ var ( FindMaintenanceWindowTaskByTwoPartKey = findMaintenanceWindowTaskByTwoPartKey FindParameterByName = findParameterByName FindPatchBaselineByID = findPatchBaselineByID + FindPatchGroupByTwoPartKey = findPatchGroupByTwoPartKey ) diff --git a/internal/service/ssm/find.go b/internal/service/ssm/find.go index b804dbb3968..b2e68d53a96 100644 --- a/internal/service/ssm/find.go +++ b/internal/service/ssm/find.go @@ -13,35 +13,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// FindPatchGroup returns matching SSM Patch Group by Patch Group and BaselineId. -func FindPatchGroup(ctx context.Context, conn *ssm.SSM, patchGroup, baselineId string) (*ssm.PatchGroupPatchBaselineMapping, error) { - input := &ssm.DescribePatchGroupsInput{} - var result *ssm.PatchGroupPatchBaselineMapping - - err := conn.DescribePatchGroupsPagesWithContext(ctx, input, func(page *ssm.DescribePatchGroupsOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, mapping := range page.Mappings { - if mapping == nil { - continue - } - - if aws.StringValue(mapping.PatchGroup) == patchGroup { - if mapping.BaselineIdentity != nil && aws.StringValue(mapping.BaselineIdentity.BaselineId) == baselineId { - result = mapping - return false - } - } - } - - return !lastPage - }) - - return result, err -} - func FindServiceSettingByID(ctx context.Context, conn *ssm.SSM, id string) (*ssm.ServiceSetting, error) { input := &ssm.GetServiceSettingInput{ SettingId: aws.String(id), diff --git a/internal/service/ssm/patch_group.go b/internal/service/ssm/patch_group.go index f22e42f9ec9..78c7c5d3e66 100644 --- a/internal/service/ssm/patch_group.go +++ b/internal/service/ssm/patch_group.go @@ -5,21 +5,27 @@ package ssm import ( "context" - "fmt" "log" - "strings" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "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/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "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" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// @SDKResource("aws_ssm_patch_group") -func ResourcePatchGroup() *schema.Resource { +const ( + patchGroupResourceIDPartCount = 2 +) + +// @SDKResource("aws_ssm_patch_group", name="Patch Group") +func resourcePatchGroup() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourcePatchGroupCreate, ReadWithoutTimeout: resourcePatchGroupRead, @@ -29,7 +35,7 @@ func ResourcePatchGroup() *schema.Resource { StateUpgraders: []schema.StateUpgrader{ { Type: resourcePatchGroupV0().CoreConfigSchema().ImpliedType(), - Upgrade: PatchGroupStateUpgradeV0, + Upgrade: patchGroupStateUpgradeV0, Version: 0, }, }, @@ -51,57 +57,54 @@ func ResourcePatchGroup() *schema.Resource { func resourcePatchGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - baselineId := d.Get("baseline_id").(string) + baselineID := d.Get("baseline_id").(string) patchGroup := d.Get("patch_group").(string) - - params := &ssm.RegisterPatchBaselineForPatchGroupInput{ - BaselineId: aws.String(baselineId), + id := errs.Must(flex.FlattenResourceId([]string{patchGroup, baselineID}, patchGroupResourceIDPartCount, false)) + input := &ssm.RegisterPatchBaselineForPatchGroupInput{ + BaselineId: aws.String(baselineID), PatchGroup: aws.String(patchGroup), } - resp, err := conn.RegisterPatchBaselineForPatchGroupWithContext(ctx, params) + _, err := conn.RegisterPatchBaselineForPatchGroup(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "registering SSM Patch Baseline (%s) for Patch Group (%s): %s", baselineId, patchGroup, err) + return sdkdiag.AppendErrorf(diags, "creating SSM Patch Group (%s): %s", id, err) } - d.SetId(fmt.Sprintf("%s,%s", aws.StringValue(resp.PatchGroup), aws.StringValue(resp.BaselineId))) + d.SetId(id) return append(diags, resourcePatchGroupRead(ctx, d, meta)...) } func resourcePatchGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - patchGroup, baselineId, err := ParsePatchGroupID(d.Id()) + parts, err := flex.ExpandResourceId(d.Id(), patchGroupResourceIDPartCount, false) if err != nil { - return sdkdiag.AppendErrorf(diags, "parsing SSM Patch Group ID (%s): %s", d.Id(), err) - } - - group, err := FindPatchGroup(ctx, conn, patchGroup, baselineId) - - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading SSM Patch Group (%s): %s", d.Id(), err) + return sdkdiag.AppendFromErr(diags, err) } - if group == nil { - if d.IsNewResource() { - return sdkdiag.AppendErrorf(diags, "reading SSM Patch Group (%s): not found after creation", d.Id()) - } + patchGroup, baselineID := parts[0], parts[1] + group, err := findPatchGroupByTwoPartKey(ctx, conn, patchGroup, baselineID) - log.Printf("[WARN] SSM Patch Group (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSM Patch Group %s not found, removing from state", d.Id()) d.SetId("") return diags } - var groupBaselineId string - if group.BaselineIdentity != nil { - groupBaselineId = aws.StringValue(group.BaselineIdentity.BaselineId) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading SSM Patch Group (%s): %s", d.Id(), err) } - d.Set("baseline_id", groupBaselineId) + var groupBaselineID string + if group.BaselineIdentity != nil { + groupBaselineID = aws.ToString(group.BaselineIdentity.BaselineId) + } + d.Set("baseline_id", groupBaselineID) d.Set("patch_group", group.PatchGroup) return diags @@ -109,36 +112,73 @@ func resourcePatchGroupRead(ctx context.Context, d *schema.ResourceData, meta in func resourcePatchGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - patchGroup, baselineId, err := ParsePatchGroupID(d.Id()) + parts, err := flex.ExpandResourceId(d.Id(), patchGroupResourceIDPartCount, false) if err != nil { return sdkdiag.AppendFromErr(diags, err) } + patchGroup, baselineID := parts[0], parts[1] + log.Printf("[WARN] Deleting SSM Patch Group: %s", d.Id()) - _, err = conn.DeregisterPatchBaselineForPatchGroupWithContext(ctx, &ssm.DeregisterPatchBaselineForPatchGroupInput{ - BaselineId: aws.String(baselineId), + _, err = conn.DeregisterPatchBaselineForPatchGroup(ctx, &ssm.DeregisterPatchBaselineForPatchGroupInput{ + BaselineId: aws.String(baselineID), PatchGroup: aws.String(patchGroup), }) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeDoesNotExistException) { + if errs.IsA[*awstypes.DoesNotExistException](err) { return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "deregistering SSM Patch Baseline (%s) for Patch Group (%s): %s", baselineId, patchGroup, err) + return sdkdiag.AppendErrorf(diags, "deleting SSM Patch Group (%s): %s", d.Id(), err) } return diags } -func ParsePatchGroupID(id string) (string, string, error) { - parts := strings.SplitN(id, ",", 2) +func findPatchGroupByTwoPartKey(ctx context.Context, conn *ssm.Client, patchGroup, baselineID string) (*awstypes.PatchGroupPatchBaselineMapping, error) { + input := &ssm.DescribePatchGroupsInput{} - if len(parts) != 2 || parts[0] == "" || parts[1] == "" { - return "", "", fmt.Errorf("please make sure ID is in format PATCH_GROUP,BASELINE_ID") + return findPatchGroup(ctx, conn, input, func(v *awstypes.PatchGroupPatchBaselineMapping) bool { + if aws.ToString(v.PatchGroup) == patchGroup { + if v.BaselineIdentity != nil && aws.ToString(v.BaselineIdentity.BaselineId) == baselineID { + return true + } + } + + return false + }) +} + +func findPatchGroup(ctx context.Context, conn *ssm.Client, input *ssm.DescribePatchGroupsInput, filter tfslices.Predicate[*awstypes.PatchGroupPatchBaselineMapping]) (*awstypes.PatchGroupPatchBaselineMapping, error) { + output, err := findPatchGroups(ctx, conn, input, filter) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findPatchGroups(ctx context.Context, conn *ssm.Client, input *ssm.DescribePatchGroupsInput, filter tfslices.Predicate[*awstypes.PatchGroupPatchBaselineMapping]) ([]awstypes.PatchGroupPatchBaselineMapping, error) { + var output []awstypes.PatchGroupPatchBaselineMapping + + pages := ssm.NewDescribePatchGroupsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if err != nil { + return nil, err + } + + for _, v := range page.Mappings { + if filter(&v) { + output = append(output, v) + } + } } - return parts[0], parts[1], nil + return output, nil } diff --git a/internal/service/ssm/patch_group_migrate.go b/internal/service/ssm/patch_group_migrate.go index f0e62bc5f43..5ac1703779f 100644 --- a/internal/service/ssm/patch_group_migrate.go +++ b/internal/service/ssm/patch_group_migrate.go @@ -28,7 +28,7 @@ func resourcePatchGroupV0() *schema.Resource { } } -func PatchGroupStateUpgradeV0(_ context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { +func patchGroupStateUpgradeV0(_ context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { if rawState == nil { rawState = map[string]interface{}{} } diff --git a/internal/service/ssm/patch_group_migrate_test.go b/internal/service/ssm/patch_group_migrate_test.go index 5a64da68f88..0db92f5f50a 100644 --- a/internal/service/ssm/patch_group_migrate_test.go +++ b/internal/service/ssm/patch_group_migrate_test.go @@ -1,15 +1,14 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package ssm_test +package ssm import ( + "context" "fmt" "reflect" "testing" - "github.com/hashicorp/terraform-provider-aws/internal/acctest" - tfssm "github.com/hashicorp/terraform-provider-aws/internal/service/ssm" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -31,11 +30,11 @@ func testResourcePatchGroupStateDataV1() map[string]interface{} { } func TestPatchGroupStateUpgradeV0(t *testing.T) { - ctx := acctest.Context(t) + ctx := context.Background() t.Parallel() expected := testResourcePatchGroupStateDataV1() - actual, err := tfssm.PatchGroupStateUpgradeV0(ctx, testResourcePatchGroupStateDataV0(), nil) + actual, err := patchGroupStateUpgradeV0(ctx, testResourcePatchGroupStateDataV0(), nil) if err != nil { t.Fatalf("error migrating state: %s", err) } diff --git a/internal/service/ssm/patch_group_test.go b/internal/service/ssm/patch_group_test.go index 675cd30d8c4..6e52caa0b5a 100644 --- a/internal/service/ssm/patch_group_test.go +++ b/internal/service/ssm/patch_group_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfssm "github.com/hashicorp/terraform-provider-aws/internal/service/ssm" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -88,27 +89,24 @@ func TestAccSSMPatchGroup_multipleBaselines(t *testing.T) { func testAccCheckPatchGroupDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ssm_patch_group" { continue } - patchGroup, baselineId, err := tfssm.ParsePatchGroupID(rs.Primary.ID) - if err != nil { - return fmt.Errorf("error parsing SSM Patch Group ID (%s): %w", rs.Primary.ID, err) - } + _, err := tfssm.FindPatchGroupByTwoPartKey(ctx, conn, rs.Primary.Attributes["patch_group"], rs.Primary.Attributes["baseline_id"]) - group, err := tfssm.FindPatchGroup(ctx, conn, patchGroup, baselineId) + if tfresource.NotFound(err) { + continue + } if err != nil { - return fmt.Errorf("error describing SSM Patch Group ID (%s): %w", rs.Primary.ID, err) + return err } - if group != nil { - return fmt.Errorf("SSM Patch Group %q still exists", rs.Primary.ID) - } + return fmt.Errorf("SSM Patch Group %s still exists", rs.Primary.ID) } return nil @@ -122,28 +120,11 @@ func testAccCheckPatchGroupExists(ctx context.Context, n string) resource.TestCh return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No SSM Patch Baseline ID is set") - } - - patchGroup, baselineId, err := tfssm.ParsePatchGroupID(rs.Primary.ID) - if err != nil { - return fmt.Errorf("error parsing SSM Patch Group ID (%s): %w", rs.Primary.ID, err) - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) - group, err := tfssm.FindPatchGroup(ctx, conn, patchGroup, baselineId) - - if err != nil { - return fmt.Errorf("error reading SSM Patch Group (%s): %w", rs.Primary.ID, err) - } + _, err := tfssm.FindPatchGroupByTwoPartKey(ctx, conn, rs.Primary.Attributes["patch_group"], rs.Primary.Attributes["baseline_id"]) - if group == nil { - return fmt.Errorf("No SSM Patch Group found") - } - - return nil + return err } } diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index a6356c0ec86..300e17005a0 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -122,8 +122,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka }, }, { - Factory: ResourcePatchGroup, + Factory: resourcePatchGroup, TypeName: "aws_ssm_patch_group", + Name: "Patch Group", }, { Factory: ResourceResourceDataSync, From 61bca039e424b446a006b4374debfd20f1a5db96 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 15:50:24 -0400 Subject: [PATCH 22/34] r/aws_ssm_resource_data_sync: Migrate to AWS SDK for Go v2. --- .changelog/#####.txt | 4 + internal/service/ssm/exports_test.go | 2 + internal/service/ssm/resource_data_sync.go | 180 ++++++++++-------- .../service/ssm/resource_data_sync_test.go | 52 +++-- internal/service/ssm/service_package_gen.go | 3 +- 5 files changed, 145 insertions(+), 96 deletions(-) diff --git a/.changelog/#####.txt b/.changelog/#####.txt index c7508a3f007..54321ead394 100644 --- a/.changelog/#####.txt +++ b/.changelog/#####.txt @@ -1,3 +1,7 @@ ```release-note:enhancement resource/aws_ssm_maintenance_window_task: Add plan-time validation of `task_arn` +``` + +```release-note:enhancement +resource/aws_ssm_resource_data_sync: Add plan-time validation of `s3_destination.kms_key_arn`, `s3_destination.region` and `s3_destination.sync_format` ``` \ No newline at end of file diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index d0ae18c1eb8..8c17829c2e5 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -15,6 +15,7 @@ var ( ResourceParameter = resourceParameter ResourcePatchBaseline = resourcePatchBaseline ResourcePatchGroup = resourcePatchGroup + ResourceResourceDataSync = resourceResourceDataSync FindActivationByID = findActivationByID FindAssociationByID = findAssociationByID @@ -27,4 +28,5 @@ var ( FindParameterByName = findParameterByName FindPatchBaselineByID = findPatchBaselineByID FindPatchGroupByTwoPartKey = findPatchGroupByTwoPartKey + FindResourceDataSyncByName = findResourceDataSyncByName ) diff --git a/internal/service/ssm/resource_data_sync.go b/internal/service/ssm/resource_data_sync.go index 6943a55adf2..9d2c9eef829 100644 --- a/internal/service/ssm/resource_data_sync.go +++ b/internal/service/ssm/resource_data_sync.go @@ -8,20 +8,23 @@ import ( "log" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "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/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/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-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" + 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_ssm_resource_data_sync") -func ResourceResourceDataSync() *schema.Resource { +// @SDKResource("aws_ssm_resource_data_sync", name="Resource Data Sync") +func resourceResourceDataSync() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceResourceDataSyncCreate, ReadWithoutTimeout: resourceResourceDataSyncRead, @@ -44,31 +47,34 @@ func ResourceResourceDataSync() *schema.Resource { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - names.AttrKMSKeyARN: { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, names.AttrBucketName: { Type: schema.TypeString, Required: true, ForceNew: true, }, + names.AttrKMSKeyARN: { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, names.AttrPrefix: { Type: schema.TypeString, Optional: true, ForceNew: true, }, names.AttrRegion: { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidRegionName, }, "sync_format": { - Type: schema.TypeString, - Optional: true, - Default: ssm.ResourceDataSyncS3FormatJsonSerDe, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: awstypes.ResourceDataSyncS3FormatJsonSerde, + ValidateDiagFunc: enum.Validate[awstypes.ResourceDataSyncS3Format](), }, }, }, @@ -79,42 +85,35 @@ func ResourceResourceDataSync() *schema.Resource { func resourceResourceDataSyncCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) name := d.Get(names.AttrName).(string) - input := &ssm.CreateResourceDataSyncInput{ S3Destination: expandResourceDataSyncS3Destination(d), SyncName: aws.String(name), } - err := retry.RetryContext(ctx, 1*time.Minute, func() *retry.RetryError { - _, err := conn.CreateResourceDataSyncWithContext(ctx, input) - if err != nil { - if tfawserr.ErrMessageContains(err, ssm.ErrCodeResourceDataSyncInvalidConfigurationException, "S3 write failed for bucket") { - return retry.RetryableError(err) - } - return retry.NonRetryableError(err) - } - return nil - }) - if tfresource.TimedOut(err) { - _, err = conn.CreateResourceDataSyncWithContext(ctx, input) - } + const ( + timeout = 1 * time.Minute + ) + _, err := tfresource.RetryWhenIsAErrorMessageContains[*awstypes.ResourceDataSyncInvalidConfigurationException](ctx, timeout, func() (interface{}, error) { + return conn.CreateResourceDataSync(ctx, input) + }, "S3 write failed for bucket") if err != nil { return sdkdiag.AppendErrorf(diags, "creating SSM Resource Data Sync (%s): %s", name, err) } d.SetId(name) + return append(diags, resourceResourceDataSyncRead(ctx, d, meta)...) } func resourceResourceDataSyncRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - syncItem, err := FindResourceDataSyncItem(ctx, conn, d.Id()) + syncItem, err := findResourceDataSyncByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] SSM Resource Data Sync (%s) not found, removing from state", d.Id()) @@ -127,83 +126,102 @@ func resourceResourceDataSyncRead(ctx context.Context, d *schema.ResourceData, m } d.Set(names.AttrName, syncItem.SyncName) - d.Set("s3_destination", flattenResourceDataSyncS3Destination(syncItem.S3Destination)) + if err := d.Set("s3_destination", flattenResourceDataSyncS3Destination(syncItem.S3Destination)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting s3_destination: %s", err) + } + return diags } func resourceResourceDataSyncDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - input := &ssm.DeleteResourceDataSyncInput{ + log.Printf("[DEBUG] Deleting SSM Resource Data Sync: %s", d.Id()) + _, err := conn.DeleteResourceDataSync(ctx, &ssm.DeleteResourceDataSyncInput{ SyncName: aws.String(d.Id()), - } - - _, err := conn.DeleteResourceDataSyncWithContext(ctx, input) + }) - if tfawserr.ErrCodeEquals(err, ssm.ErrCodeResourceDataSyncNotFoundException) { + if errs.IsA[*awstypes.ResourceDataSyncNotFoundException](err) { return diags } if err != nil { return sdkdiag.AppendErrorf(diags, "deleting SSM Resource Data Sync (%s): %s", d.Id(), err) } + return diags } -func FindResourceDataSyncItem(ctx context.Context, conn *ssm.SSM, name string) (*ssm.ResourceDataSyncItem, error) { - var result *ssm.ResourceDataSyncItem +func findResourceDataSyncByName(ctx context.Context, conn *ssm.Client, name string) (*awstypes.ResourceDataSyncItem, error) { input := &ssm.ListResourceDataSyncInput{} - err := conn.ListResourceDataSyncPagesWithContext(ctx, input, func(page *ssm.ListResourceDataSyncOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, item := range page.ResourceDataSyncItems { - if aws.StringValue(item.SyncName) == name { - result = item - return false - } - } - - return !lastPage + return findResourceDataSync(ctx, conn, input, func(v *awstypes.ResourceDataSyncItem) bool { + return aws.ToString(v.SyncName) == name }) +} + +func findResourceDataSync(ctx context.Context, conn *ssm.Client, input *ssm.ListResourceDataSyncInput, filter tfslices.Predicate[*awstypes.ResourceDataSyncItem]) (*awstypes.ResourceDataSyncItem, error) { + output, err := findResourceDataSyncs(ctx, conn, input, filter) if err != nil { return nil, err } - if result == nil { - return nil, &retry.NotFoundError{} + + return tfresource.AssertSingleValueResult(output) +} + +func findResourceDataSyncs(ctx context.Context, conn *ssm.Client, input *ssm.ListResourceDataSyncInput, filter tfslices.Predicate[*awstypes.ResourceDataSyncItem]) ([]awstypes.ResourceDataSyncItem, error) { + var output []awstypes.ResourceDataSyncItem + + pages := ssm.NewListResourceDataSyncPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if err != nil { + return nil, err + } + + for _, v := range page.ResourceDataSyncItems { + if filter(&v) { + output = append(output, v) + } + } } - return result, nil + + return output, nil } -func flattenResourceDataSyncS3Destination(dest *ssm.ResourceDataSyncS3Destination) []interface{} { - result := make(map[string]interface{}) - result[names.AttrBucketName] = aws.StringValue(dest.BucketName) - result[names.AttrRegion] = aws.StringValue(dest.Region) - result["sync_format"] = aws.StringValue(dest.SyncFormat) - if dest.AWSKMSKeyARN != nil { - result[names.AttrKMSKeyARN] = aws.StringValue(dest.AWSKMSKeyARN) +func flattenResourceDataSyncS3Destination(apiObject *awstypes.ResourceDataSyncS3Destination) []interface{} { + tfMap := make(map[string]interface{}) + + tfMap[names.AttrBucketName] = aws.ToString(apiObject.BucketName) + tfMap[names.AttrRegion] = aws.ToString(apiObject.Region) + tfMap["sync_format"] = apiObject.SyncFormat + if apiObject.AWSKMSKeyARN != nil { + tfMap[names.AttrKMSKeyARN] = aws.ToString(apiObject.AWSKMSKeyARN) } - if dest.Prefix != nil { - result[names.AttrPrefix] = aws.StringValue(dest.Prefix) + if apiObject.Prefix != nil { + tfMap[names.AttrPrefix] = aws.ToString(apiObject.Prefix) } - return []interface{}{result} + + return []interface{}{tfMap} } -func expandResourceDataSyncS3Destination(d *schema.ResourceData) *ssm.ResourceDataSyncS3Destination { - raw := d.Get("s3_destination").([]interface{})[0].(map[string]interface{}) - s3dest := &ssm.ResourceDataSyncS3Destination{ - BucketName: aws.String(raw[names.AttrBucketName].(string)), - Region: aws.String(raw[names.AttrRegion].(string)), - SyncFormat: aws.String(raw["sync_format"].(string)), +func expandResourceDataSyncS3Destination(d *schema.ResourceData) *awstypes.ResourceDataSyncS3Destination { + tfMap := d.Get("s3_destination").([]interface{})[0].(map[string]interface{}) + apiObject := &awstypes.ResourceDataSyncS3Destination{ + BucketName: aws.String(tfMap[names.AttrBucketName].(string)), + Region: aws.String(tfMap[names.AttrRegion].(string)), + SyncFormat: awstypes.ResourceDataSyncS3Format(tfMap["sync_format"].(string)), } - if v, ok := raw[names.AttrKMSKeyARN].(string); ok && v != "" { - s3dest.AWSKMSKeyARN = aws.String(v) + + if v, ok := tfMap[names.AttrKMSKeyARN].(string); ok && v != "" { + apiObject.AWSKMSKeyARN = aws.String(v) } - if v, ok := raw[names.AttrPrefix].(string); ok && v != "" { - s3dest.Prefix = aws.String(v) + + if v, ok := tfMap[names.AttrPrefix].(string); ok && v != "" { + apiObject.Prefix = aws.String(v) } - return s3dest + + return apiObject } diff --git a/internal/service/ssm/resource_data_sync_test.go b/internal/service/ssm/resource_data_sync_test.go index 31c2e54e827..1013d5bb262 100644 --- a/internal/service/ssm/resource_data_sync_test.go +++ b/internal/service/ssm/resource_data_sync_test.go @@ -6,7 +6,6 @@ package ssm_test import ( "context" "fmt" - "log" "testing" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" @@ -32,7 +31,7 @@ func TestAccSSMResourceDataSync_basic(t *testing.T) { { Config: testAccResourceDataSyncConfig_basic(sdkacctest.RandInt(), sdkacctest.RandString(5)), Check: resource.ComposeTestCheckFunc( - testAccCheckResourceDataSyncExists(resourceName), + testAccCheckResourceDataSyncExists(ctx, resourceName), ), }, { @@ -44,6 +43,28 @@ func TestAccSSMResourceDataSync_basic(t *testing.T) { }) } +func TestAccSSMResourceDataSync_disappears(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_ssm_resource_data_sync.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckResourceDataSyncDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccResourceDataSyncConfig_basic(sdkacctest.RandInt(), sdkacctest.RandString(5)), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourceDataSyncExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfssm.ResourceResourceDataSync(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func TestAccSSMResourceDataSync_update(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandString(5) @@ -58,7 +79,7 @@ func TestAccSSMResourceDataSync_update(t *testing.T) { { Config: testAccResourceDataSyncConfig_basic(sdkacctest.RandInt(), rName), Check: resource.ComposeTestCheckFunc( - testAccCheckResourceDataSyncExists(resourceName), + testAccCheckResourceDataSyncExists(ctx, resourceName), ), }, { @@ -69,7 +90,7 @@ func TestAccSSMResourceDataSync_update(t *testing.T) { { Config: testAccResourceDataSyncConfig_update(sdkacctest.RandInt(), rName), Check: resource.ComposeTestCheckFunc( - testAccCheckResourceDataSyncExists(resourceName), + testAccCheckResourceDataSyncExists(ctx, resourceName), ), }, }, @@ -78,14 +99,14 @@ func TestAccSSMResourceDataSync_update(t *testing.T) { func testAccCheckResourceDataSyncDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ssm_resource_data_sync" { continue } - syncItem, err := tfssm.FindResourceDataSyncItem(ctx, conn, rs.Primary.ID) + _, err := tfssm.FindResourceDataSyncByName(ctx, conn, rs.Primary.ID) if tfresource.NotFound(err) { continue @@ -95,22 +116,25 @@ func testAccCheckResourceDataSyncDestroy(ctx context.Context) resource.TestCheck return err } - if syncItem != nil { - return fmt.Errorf("Resource Data Sync (%s) found", rs.Primary.ID) - } + return fmt.Errorf("SSM Resource Data Sync %s still exists", rs.Primary.ID) } + return nil } } -func testAccCheckResourceDataSyncExists(name string) resource.TestCheckFunc { +func testAccCheckResourceDataSyncExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { - log.Println(s.RootModule().Resources) - _, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", name) + return fmt.Errorf("Not found: %s", n) } - return nil + + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) + + _, err := tfssm.FindResourceDataSyncByName(ctx, conn, rs.Primary.ID) + + return err } } diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index 300e17005a0..af7fda05a6c 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -127,8 +127,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Patch Group", }, { - Factory: ResourceResourceDataSync, + Factory: resourceResourceDataSync, TypeName: "aws_ssm_resource_data_sync", + Name: "Resource Data Sync", }, { Factory: ResourceServiceSetting, From 01a518057c3c9a47ff8634c07b9076ec9a61e6aa Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 16:29:19 -0400 Subject: [PATCH 23/34] r/aws_ssm_service_setting: Migrate to AWS SDK for Go v2. --- internal/service/ssm/exports_test.go | 2 + internal/service/ssm/find.go | 39 ----- internal/service/ssm/service_package_gen.go | 3 +- internal/service/ssm/service_setting.go | 147 ++++++++++++++----- internal/service/ssm/service_setting_test.go | 50 +++++-- internal/service/ssm/status.go | 29 ---- internal/service/ssm/wait.go | 42 ------ 7 files changed, 152 insertions(+), 160 deletions(-) delete mode 100644 internal/service/ssm/find.go delete mode 100644 internal/service/ssm/status.go delete mode 100644 internal/service/ssm/wait.go diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index 8c17829c2e5..7f8a84ee002 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -16,6 +16,7 @@ var ( ResourcePatchBaseline = resourcePatchBaseline ResourcePatchGroup = resourcePatchGroup ResourceResourceDataSync = resourceResourceDataSync + ResourceServiceSetting = resourceServiceSetting FindActivationByID = findActivationByID FindAssociationByID = findAssociationByID @@ -29,4 +30,5 @@ var ( FindPatchBaselineByID = findPatchBaselineByID FindPatchGroupByTwoPartKey = findPatchGroupByTwoPartKey FindResourceDataSyncByName = findResourceDataSyncByName + FindServiceSettingByID = findServiceSettingByID ) diff --git a/internal/service/ssm/find.go b/internal/service/ssm/find.go deleted file mode 100644 index b2e68d53a96..00000000000 --- a/internal/service/ssm/find.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package ssm - -import ( - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" -) - -func FindServiceSettingByID(ctx context.Context, conn *ssm.SSM, id string) (*ssm.ServiceSetting, error) { - input := &ssm.GetServiceSettingInput{ - SettingId: aws.String(id), - } - - output, err := conn.GetServiceSettingWithContext(ctx, input) - - if tfawserr.ErrCodeContains(err, ssm.ErrCodeServiceSettingNotFound) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil || output.ServiceSetting == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output.ServiceSetting, nil -} diff --git a/internal/service/ssm/service_package_gen.go b/internal/service/ssm/service_package_gen.go index af7fda05a6c..b911e12e86d 100644 --- a/internal/service/ssm/service_package_gen.go +++ b/internal/service/ssm/service_package_gen.go @@ -132,8 +132,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Name: "Resource Data Sync", }, { - Factory: ResourceServiceSetting, + Factory: resourceServiceSetting, TypeName: "aws_ssm_service_setting", + Name: "Service Setting", }, } } diff --git a/internal/service/ssm/service_setting.go b/internal/service/ssm/service_setting.go index 634ead69422..b44a8b11373 100644 --- a/internal/service/ssm/service_setting.go +++ b/internal/service/ssm/service_setting.go @@ -6,27 +6,29 @@ package ssm import ( "context" "log" + "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/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-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" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -const ( - ResNameServiceSetting = "Service Setting" -) - -// @SDKResource("aws_ssm_service_setting") -func ResourceServiceSetting() *schema.Resource { +// @SDKResource("aws_ssm_service_setting", name="Service Setting") +func resourceServiceSetting() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceServiceSettingUpdate, ReadWithoutTimeout: resourceServiceSettingRead, UpdateWithoutTimeout: resourceServiceSettingUpdate, - DeleteWithoutTimeout: resourceServiceSettingReset, + DeleteWithoutTimeout: resourceServiceSettingDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -54,23 +56,24 @@ func ResourceServiceSetting() *schema.Resource { func resourceServiceSettingUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - log.Printf("[DEBUG] SSM service setting create: %s", d.Get("setting_id").(string)) - - updateServiceSettingInput := &ssm.UpdateServiceSettingInput{ - SettingId: aws.String(d.Get("setting_id").(string)), + settingID := d.Get("setting_id").(string) + input := &ssm.UpdateServiceSettingInput{ + SettingId: aws.String(settingID), SettingValue: aws.String(d.Get("setting_value").(string)), } - if _, err := conn.UpdateServiceSettingWithContext(ctx, updateServiceSettingInput); err != nil { - return create.DiagError(names.SSM, create.ErrActionUpdating, ResNameServiceSetting, d.Get("setting_id").(string), err) + _, err := conn.UpdateServiceSetting(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating SSM Service Setting (%s): %s", settingID, err) } - d.SetId(d.Get("setting_id").(string)) + d.SetId(settingID) if _, err := waitServiceSettingUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { - return create.DiagError(names.SSM, create.ErrActionWaitingForUpdate, ResNameServiceSetting, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "waiting for SSM Service Setting (%s) update: %s", d.Id(), err) } return append(diags, resourceServiceSettingRead(ctx, d, meta)...) @@ -78,43 +81,121 @@ func resourceServiceSettingUpdate(ctx context.Context, d *schema.ResourceData, m func resourceServiceSettingRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) - log.Printf("[DEBUG] Reading SSM Activation: %s", d.Id()) + output, err := findServiceSettingByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SSM Service Setting %s not found, removing from state", d.Id()) + d.SetId("") + return diags + } - output, err := FindServiceSettingByID(ctx, conn, d.Id()) if err != nil { - return create.DiagError(names.SSM, create.ErrActionReading, ResNameServiceSetting, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading SSM Service Setting (%s): %s", d.Id(), err) } + d.Set(names.AttrARN, output.ARN) // AWS SSM service setting API requires the entire ARN as input, // but setting_id in the output is only a part of ARN. d.Set("setting_id", output.ARN) d.Set("setting_value", output.SettingValue) - d.Set(names.AttrARN, output.ARN) d.Set(names.AttrStatus, output.Status) return diags } -func resourceServiceSettingReset(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceServiceSettingDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SSMConn(ctx) + conn := meta.(*conns.AWSClient).SSMClient(ctx) log.Printf("[DEBUG] Deleting SSM Service Setting: %s", d.Id()) + _, err := conn.ResetServiceSetting(ctx, &ssm.ResetServiceSettingInput{ + SettingId: aws.String(d.Id()), + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting SSM Service Setting (%s): %s", d.Id(), err) + } + + if _, err := waitServiceSettingReset(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for SSM Service Setting (%s) delete: %s", d.Id(), err) + } + + return diags +} + +func findServiceSettingByID(ctx context.Context, conn *ssm.Client, id string) (*awstypes.ServiceSetting, error) { + input := &ssm.GetServiceSettingInput{ + SettingId: aws.String(id), + } + + output, err := conn.GetServiceSetting(ctx, input) - resetServiceSettingInput := &ssm.ResetServiceSettingInput{ - SettingId: aws.String(d.Get("setting_id").(string)), + if errs.IsA[*awstypes.ServiceSettingNotFound](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } } - _, err := conn.ResetServiceSettingWithContext(ctx, resetServiceSettingInput) if err != nil { - return create.DiagError(names.SSM, create.ErrActionDeleting, ResNameServiceSetting, d.Id(), err) + return nil, err } - if err := waitServiceSettingReset(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return create.DiagError(names.SSM, create.ErrActionWaitingForDeletion, ResNameServiceSetting, d.Id(), err) + if output == nil || output.ServiceSetting == nil { + return nil, tfresource.NewEmptyResultError(input) } - return diags + return output.ServiceSetting, nil +} + +func statusServiceSetting(ctx context.Context, conn *ssm.Client, id string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findServiceSettingByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.ToString(output.Status), nil + } +} + +func waitServiceSettingUpdated(ctx context.Context, conn *ssm.Client, id string, timeout time.Duration) (*awstypes.ServiceSetting, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{"PendingUpdate", ""}, + Target: []string{"Customized", "Default"}, + Refresh: statusServiceSetting(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*awstypes.ServiceSetting); ok { + return output, err + } + + return nil, err +} + +func waitServiceSettingReset(ctx context.Context, conn *ssm.Client, id string, timeout time.Duration) (*awstypes.ServiceSetting, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{"Customized", "PendingUpdate", ""}, + Target: []string{"Default"}, + Refresh: statusServiceSetting(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*awstypes.ServiceSetting); ok { + return output, err + } + + return nil, err } diff --git a/internal/service/ssm/service_setting_test.go b/internal/service/ssm/service_setting_test.go index ec7ac6e4ee9..eebd7b406b3 100644 --- a/internal/service/ssm/service_setting_test.go +++ b/internal/service/ssm/service_setting_test.go @@ -8,13 +8,12 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go-v2/aws" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "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" - "github.com/hashicorp/terraform-provider-aws/internal/create" tfssm "github.com/hashicorp/terraform-provider-aws/internal/service/ssm" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" @@ -22,7 +21,7 @@ import ( func TestAccSSMServiceSetting_basic(t *testing.T) { ctx := acctest.Context(t) - var setting ssm.ServiceSetting + var setting awstypes.ServiceSetting resourceName := "aws_ssm_service_setting.test" resource.ParallelTest(t, resource.TestCase{ @@ -54,16 +53,39 @@ func TestAccSSMServiceSetting_basic(t *testing.T) { }) } +func TestAccSSMServiceSetting_disappears(t *testing.T) { + ctx := acctest.Context(t) + var setting awstypes.ServiceSetting + resourceName := "aws_ssm_service_setting.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckServiceSettingDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccServiceSettingConfig_basic("false"), + Check: resource.ComposeTestCheckFunc( + testAccServiceSettingExists(ctx, resourceName, &setting), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfssm.ResourceServiceSetting(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccCheckServiceSettingDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_ssm_service_setting" { continue } - output, err := tfssm.FindServiceSettingByID(ctx, conn, rs.Primary.Attributes["setting_id"]) + output, err := tfssm.FindServiceSettingByID(ctx, conn, rs.Primary.ID) if tfresource.NotFound(err) { continue @@ -73,34 +95,30 @@ func testAccCheckServiceSettingDestroy(ctx context.Context) resource.TestCheckFu return err } - if aws.StringValue(output.Status) == "Default" { + if aws.ToString(output.Status) == "Default" { continue } - return create.Error(names.SSM, create.ErrActionCheckingDestroyed, tfssm.ResNameServiceSetting, rs.Primary.Attributes["setting_id"], err) + return fmt.Errorf("SSM Service Setting %s still exists", rs.Primary.ID) } return nil } } -func testAccServiceSettingExists(ctx context.Context, n string, v *ssm.ServiceSetting) resource.TestCheckFunc { +func testAccServiceSettingExists(ctx context.Context, n string, v *awstypes.ServiceSetting) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No SSM Service Setting ID is set") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) - output, err := tfssm.FindServiceSettingByID(ctx, conn, rs.Primary.Attributes["setting_id"]) + output, err := tfssm.FindServiceSettingByID(ctx, conn, rs.Primary.ID) if err != nil { - return create.Error(names.SSM, create.ErrActionReading, tfssm.ResNameServiceSetting, rs.Primary.Attributes["setting_id"], err) + return err } *v = *output diff --git a/internal/service/ssm/status.go b/internal/service/ssm/status.go deleted file mode 100644 index 6eaaee50550..00000000000 --- a/internal/service/ssm/status.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package ssm - -import ( - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ssm" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" -) - -func statusServiceSetting(ctx context.Context, conn *ssm.SSM, id string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - output, err := FindServiceSettingByID(ctx, conn, id) - - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - return output, aws.StringValue(output.Status), nil - } -} diff --git a/internal/service/ssm/wait.go b/internal/service/ssm/wait.go deleted file mode 100644 index f5a5d802ac2..00000000000 --- a/internal/service/ssm/wait.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package ssm - -import ( - "context" - "time" - - "github.com/aws/aws-sdk-go/service/ssm" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" -) - -func waitServiceSettingUpdated(ctx context.Context, conn *ssm.SSM, id string, timeout time.Duration) (*ssm.ServiceSetting, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{"PendingUpdate", ""}, - Target: []string{"Customized", "Default"}, - Refresh: statusServiceSetting(ctx, conn, id), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if output, ok := outputRaw.(*ssm.ServiceSetting); ok { - return output, err - } - - return nil, err -} - -func waitServiceSettingReset(ctx context.Context, conn *ssm.SSM, id string, timeout time.Duration) error { - stateConf := &retry.StateChangeConf{ - Pending: []string{"Customized", "PendingUpdate", ""}, - Target: []string{"Default"}, - Refresh: statusServiceSetting(ctx, conn, id), - Timeout: timeout, - } - - _, err := stateConf.WaitForStateContext(ctx) - - return err -} From 3115a31ffa3487b72eed6cfa8de8792bad7ad8d8 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 16:44:28 -0400 Subject: [PATCH 24/34] ssm: Migrate sweepers to AWS SDK for Go v2. --- internal/service/ssm/sweep.go | 219 +++++++++++++++------------------- 1 file changed, 93 insertions(+), 126 deletions(-) diff --git a/internal/service/ssm/sweep.go b/internal/service/ssm/sweep.go index bac881365f1..ba5618b26ce 100644 --- a/internal/service/ssm/sweep.go +++ b/internal/service/ssm/sweep.go @@ -5,22 +5,17 @@ package ssm import ( "context" - "errors" "fmt" "log" "time" "github.com/aws/aws-sdk-go-v2/aws" - ssm_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ssm" - "github.com/aws/aws-sdk-go-v2/service/ssm/types" - ssm_sdkv1 "github.com/aws/aws-sdk-go/service/ssm" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" - "github.com/hashicorp/go-multierror" + "github.com/aws/aws-sdk-go-v2/service/ssm" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "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/sweep" - "github.com/hashicorp/terraform-provider-aws/internal/sweep/awsv1" "github.com/hashicorp/terraform-provider-aws/internal/sweep/awsv2" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" @@ -63,115 +58,98 @@ func sweepDefaultPatchBaselines(region string) error { if err != nil { return fmt.Errorf("getting client: %w", err) } - conn := client.SSMClient(ctx) - - var sweepables []sweep.Sweepable - var errs *multierror.Error + sweepResources := make([]sweep.Sweepable, 0) paginator := patchBaselinesPaginator(conn, ownerIsSelfFilter()) for paginator.HasMorePages() { page, err := paginator.NextPage(ctx) + if awsv2.SkipSweepError(err) { - log.Printf("[WARN] Skipping Default Patch Baselines sweep for %s: %s", region, errs) - break + log.Printf("[WARN] Skipping SSM Default Patch Baseline sweep for %s: %s", region, err) + return nil } + if err != nil { - errs = multierror.Append(errs, fmt.Errorf("listing Default Patch Baselines for %s: %w", region, err)) - break + return fmt.Errorf("error listing SSM Default Patch Baselines (%s): %w", region, err) } - for _, identity := range tfslices.Filter(page.BaselineIdentities, func(b types.PatchBaselineIdentity) bool { - return b.DefaultBaseline + for _, identity := range tfslices.Filter(page.BaselineIdentities, func(v awstypes.PatchBaselineIdentity) bool { + return v.DefaultBaseline }) { baselineID := aws.ToString(identity.BaselineId) - pb, err := findPatchBaselineByIDV2(ctx, conn, baselineID) + pb, err := findPatchBaselineByID(ctx, conn, baselineID) + if err != nil { - errs = multierror.Append(errs, fmt.Errorf("reading Patch Baseline (%s): %w", baselineID, err)) continue } - sweepables = append(sweepables, defaultPatchBaselineSweeper{ + sweepResources = append(sweepResources, defaultPatchBaselineSweeper{ conn: conn, os: pb.OperatingSystem, }) } } - if err := sweep.SweepOrchestrator(ctx, sweepables); err != nil { - errs = multierror.Append(errs, fmt.Errorf("sweeping Default Patch Baselines for %s: %w", region, err)) + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping SSM Default Patch Baselines (%s): %w", region, err) } - return errs.ErrorOrNil() + return nil } type defaultPatchBaselineSweeper struct { - conn *ssm_sdkv2.Client - os types.OperatingSystem + conn *ssm.Client + os awstypes.OperatingSystem } -func (s defaultPatchBaselineSweeper) Delete(ctx context.Context, timeout time.Duration, optFns ...tfresource.OptionsFunc) (err error) { +func (s defaultPatchBaselineSweeper) Delete(ctx context.Context, timeout time.Duration, optFns ...tfresource.OptionsFunc) error { diags := defaultPatchBaselineRestoreOSDefault(ctx, s.conn, s.os) for _, d := range sdkdiag.Warnings(diags) { log.Printf("[WARN] %s", sdkdiag.DiagnosticString(d)) } - for _, d := range sdkdiag.Errors(diags) { - err = multierror.Append(err, errors.New(sdkdiag.DiagnosticString(d))) - } - return + return sdkdiag.DiagnosticsError(diags) } func sweepMaintenanceWindows(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) - if err != nil { return fmt.Errorf("getting client: %s", err) } + conn := client.SSMClient(ctx) + input := &ssm.DescribeMaintenanceWindowsInput{} + sweepResources := make([]sweep.Sweepable, 0) - conn := client.SSMConn(ctx) - - input := &ssm_sdkv1.DescribeMaintenanceWindowsInput{} - var sweeperErrs *multierror.Error - - for { - output, err := conn.DescribeMaintenanceWindowsWithContext(ctx, input) + pages := ssm.NewDescribeMaintenanceWindowsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) - if awsv1.SkipSweepError(err) { + if awsv2.SkipSweepError(err) { log.Printf("[WARN] Skipping SSM Maintenance Window sweep for %s: %s", region, err) return nil } if err != nil { - return fmt.Errorf("Error retrieving SSM Maintenance Windows: %s", err) + return fmt.Errorf("error listing SSM Maintenance Windows (%s): %w", region, err) } - for _, window := range output.WindowIdentities { - id := aws.ToString(window.WindowId) - input := &ssm_sdkv1.DeleteMaintenanceWindowInput{ - WindowId: window.WindowId, - } - - log.Printf("[INFO] Deleting SSM Maintenance Window: %s", id) - - _, err := conn.DeleteMaintenanceWindowWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, ssm_sdkv1.ErrCodeDoesNotExistException) { - continue - } + for _, v := range page.WindowIdentities { + r := resourceMaintenanceWindow() + d := r.Data(nil) + d.SetId(aws.ToString(v.WindowId)) - if err != nil { - sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("deleting SSM Maintenance Window (%s): %w", id, err)) - continue - } + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } + } - if aws.ToString(output.NextToken) == "" { - break - } + err = sweep.SweepOrchestrator(ctx, sweepResources) - input.NextToken = output.NextToken + if err != nil { + return fmt.Errorf("error sweeping SSM Maintenance Windows (%s): %w", region, err) } return nil @@ -183,40 +161,39 @@ func sweepPatchBaselines(region string) error { if err != nil { return fmt.Errorf("getting client: %w", err) } - conn := client.SSMClient(ctx) - - var sweepables []sweep.Sweepable - var errs *multierror.Error + sweepResources := make([]sweep.Sweepable, 0) paginator := patchBaselinesPaginator(conn, ownerIsSelfFilter()) for paginator.HasMorePages() { page, err := paginator.NextPage(ctx) + if awsv2.SkipSweepError(err) { - log.Printf("[WARN] Skipping Patch Baselines sweep for %s: %s", region, errs) - break + log.Printf("[WARN] Skipping SSM Patch Baseline sweep for %s: %s", region, err) + return nil } + if err != nil { - errs = multierror.Append(errs, fmt.Errorf("listing Patch Baselines for %s: %w", region, err)) - break + return fmt.Errorf("error listing SSM Patch Baselines (%s): %w", region, err) } - for _, identity := range page.BaselineIdentities { - baselineID := aws.ToString(identity.BaselineId) + for _, v := range page.BaselineIdentities { r := resourcePatchBaseline() d := r.Data(nil) - d.SetId(baselineID) - d.Set("operating_system", identity.OperatingSystem) + d.SetId(aws.ToString(v.BaselineId)) + d.Set("operating_system", v.OperatingSystem) - sweepables = append(sweepables, sweep.NewSweepResource(r, d, client)) + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } } - if err := sweep.SweepOrchestrator(ctx, sweepables); err != nil { - errs = multierror.Append(errs, fmt.Errorf("sweeping Patch Baselines for %s: %w", region, err)) + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping SSM Patch Baselines (%s): %w", region, err) } - return errs.ErrorOrNil() + return nil } func sweepPatchGroups(region string) error { @@ -225,89 +202,79 @@ func sweepPatchGroups(region string) error { if err != nil { return fmt.Errorf("getting client: %w", err) } - conn := client.SSMConn(ctx) + conn := client.SSMClient(ctx) + input := &ssm.DescribePatchGroupsInput{} sweepResources := make([]sweep.Sweepable, 0) - var errs *multierror.Error - input := &ssm_sdkv1.DescribePatchGroupsInput{} + pages := ssm.NewDescribePatchGroupsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping SSM Patch Group sweep for %s: %s", region, err) + return nil + } - err = conn.DescribePatchGroupsPagesWithContext(ctx, input, func(page *ssm_sdkv1.DescribePatchGroupsOutput, lastPage bool) bool { - if page == nil { - return !lastPage + if err != nil { + return fmt.Errorf("error listing SSM Patch Groups (%s): %w", region, err) } - for _, mapping := range page.Mappings { - r := ResourcePatchGroup() + for _, v := range page.Mappings { + r := resourcePatchGroup() d := r.Data(nil) - d.SetId(fmt.Sprintf("%s,%s", aws.ToString(mapping.PatchGroup), aws.ToString(mapping.BaselineIdentity.BaselineId))) + d.SetId(fmt.Sprintf("%s,%s", aws.ToString(v.PatchGroup), aws.ToString(v.BaselineIdentity.BaselineId))) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } - - return !lastPage - }) - - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("listing SSM Patch Groups for %s: %w", region, err)) } - if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("sweeping SSM Patch Groups for %s: %w", region, err)) - } + err = sweep.SweepOrchestrator(ctx, sweepResources) - if awsv1.SkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping SSM Patch Group sweep for %s: %s", region, errs) - return nil + if err != nil { + return fmt.Errorf("error sweeping SSM Patch Groups (%s): %w", region, err) } - return errs.ErrorOrNil() + return nil } func sweepResourceDataSyncs(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) - if err != nil { return fmt.Errorf("getting client: %w", err) } - - conn := client.SSMConn(ctx) - + conn := client.SSMClient(ctx) + input := &ssm.ListResourceDataSyncInput{} sweepResources := make([]sweep.Sweepable, 0) - var errs *multierror.Error - input := &ssm_sdkv1.ListResourceDataSyncInput{} + pages := ssm.NewListResourceDataSyncPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) - err = conn.ListResourceDataSyncPagesWithContext(ctx, input, func(page *ssm_sdkv1.ListResourceDataSyncOutput, lastPage bool) bool { - if page == nil { - return !lastPage + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping SSM Resource Data Sync sweep for %s: %s", region, err) + return nil } - for _, resourceDataSync := range page.ResourceDataSyncItems { - r := ResourceResourceDataSync() - d := r.Data(nil) + if err != nil { + return fmt.Errorf("error listing SSM Resource Data Syncs (%s): %w", region, err) + } - d.SetId(aws.ToString(resourceDataSync.SyncName)) - d.Set(names.AttrName, resourceDataSync.SyncName) + for _, v := range page.ResourceDataSyncItems { + r := resourceResourceDataSync() + d := r.Data(nil) + d.SetId(aws.ToString(v.SyncName)) + d.Set(names.AttrName, v.SyncName) sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } - - return !lastPage - }) - - if err != nil { - errs = multierror.Append(errs, fmt.Errorf("listing SSM Resource Data Sync for %s: %w", region, err)) } - if err := sweep.SweepOrchestrator(ctx, sweepResources); err != nil { - errs = multierror.Append(errs, fmt.Errorf("sweeping SSM Resource Data Sync for %s: %w", region, err)) - } + err = sweep.SweepOrchestrator(ctx, sweepResources) - if awsv1.SkipSweepError(errs.ErrorOrNil()) { - log.Printf("[WARN] Skipping SSM Resource Data Sync sweep for %s: %s", region, errs) - return nil + if err != nil { + return fmt.Errorf("error sweeping SSM Resource Data Syncs (%s): %w", region, err) } - return errs.ErrorOrNil() + return nil } From f80f8b1a07997bb7cbfd6ebad8d5ca446f103f6f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 16:54:04 -0400 Subject: [PATCH 25/34] Fix golangci-lint 'whitespace'. --- internal/service/ssm/patch_baseline_data_source.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/ssm/patch_baseline_data_source.go b/internal/service/ssm/patch_baseline_data_source.go index e3b1ffc3c7f..a698e313227 100644 --- a/internal/service/ssm/patch_baseline_data_source.go +++ b/internal/service/ssm/patch_baseline_data_source.go @@ -214,7 +214,6 @@ Baselines: if err != nil { return sdkdiag.AppendFromErr(diags, tfresource.SingularDataSourceFindError("SSM Patch Baseline", err)) - } id := aws.ToString(baseline.BaselineId) From 37af6076640e2de1d2cead6ad407d6b9cc5e468e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 16:59:15 -0400 Subject: [PATCH 26/34] Fix golangci-lint 'durationcheck'. --- internal/service/ssm/association.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/service/ssm/association.go b/internal/service/ssm/association.go index 0a23f755aaf..22a5886a85d 100644 --- a/internal/service/ssm/association.go +++ b/internal/service/ssm/association.go @@ -242,7 +242,8 @@ func resourceAssociationCreate(ctx context.Context, d *schema.ResourceData, meta d.SetId(aws.ToString(output.AssociationDescription.AssociationId)) if v, ok := d.GetOk("wait_for_success_timeout_seconds"); ok { - if _, err := waitAssociationCreated(ctx, conn, d.Id(), time.Duration(v.(int))*time.Second); err != nil { + timeout := time.Duration(v.(int)) * time.Second //nolint:durationcheck // should really be d.Timeout(schema.TimeoutCreate) + if _, err := waitAssociationCreated(ctx, conn, d.Id(), timeout); err != nil { return sdkdiag.AppendErrorf(diags, "waiting for SSM Association (%s) create: %s", d.Id(), err) } } From ad8d7c759aa08d3902b0a1ac0f17135428cd3f74 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 13 May 2024 17:01:18 -0400 Subject: [PATCH 27/34] Correct CHANGELOG entry file name. --- .changelog/{#####.txt => 37481.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changelog/{#####.txt => 37481.txt} (100%) diff --git a/.changelog/#####.txt b/.changelog/37481.txt similarity index 100% rename from .changelog/#####.txt rename to .changelog/37481.txt From 6712f09db8bfe1079848ba87438620761a79cf5b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 14 May 2024 07:44:29 -0400 Subject: [PATCH 28/34] r/aws_ssm_maintenance_window_task: Don't validate 'task-arn'. --- .changelog/37481.txt | 4 ---- internal/service/ssm/maintenance_window_task.go | 5 ++--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.changelog/37481.txt b/.changelog/37481.txt index 54321ead394..96bb9956302 100644 --- a/.changelog/37481.txt +++ b/.changelog/37481.txt @@ -1,7 +1,3 @@ -```release-note:enhancement -resource/aws_ssm_maintenance_window_task: Add plan-time validation of `task_arn` -``` - ```release-note:enhancement resource/aws_ssm_resource_data_sync: Add plan-time validation of `s3_destination.kms_key_arn`, `s3_destination.region` and `s3_destination.sync_format` ``` \ No newline at end of file diff --git a/internal/service/ssm/maintenance_window_task.go b/internal/service/ssm/maintenance_window_task.go index cce8991d877..ccae180c6a0 100644 --- a/internal/service/ssm/maintenance_window_task.go +++ b/internal/service/ssm/maintenance_window_task.go @@ -106,9 +106,8 @@ func resourceMaintenanceWindowTask() *schema.Resource { }, }, "task_arn": { - Type: schema.TypeString, - Required: true, - ValidateFunc: verify.ValidARN, + Type: schema.TypeString, + Required: true, }, "task_invocation_parameters": { Type: schema.TypeList, From af4193b8e2d4e042daf2d7f9763226b5ca76c575 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 14 May 2024 07:57:40 -0400 Subject: [PATCH 29/34] r/aws_ssm_patch_baseline: Fix 'ResourceInUseException: PatchBaseline to be deleted is set as default' errors. --- internal/service/ssm/patch_baseline.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/ssm/patch_baseline.go b/internal/service/ssm/patch_baseline.go index 9f7dc05b96b..888f349f0a7 100644 --- a/internal/service/ssm/patch_baseline.go +++ b/internal/service/ssm/patch_baseline.go @@ -419,7 +419,7 @@ func resourcePatchBaselineDelete(ctx context.Context, d *schema.ResourceData, me _, err := conn.DeletePatchBaseline(ctx, input) - if errs.IsA[*awstypes.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceInUseException](err) { // Reset the default patch baseline before retrying. diags = append(diags, defaultPatchBaselineRestoreOSDefault(ctx, meta.(*conns.AWSClient).SSMClient(ctx), awstypes.OperatingSystem(d.Get("operating_system").(string)))...) if diags.HasError() { From 90ed97ed4aec2822c59a46d7478d1d026b540913 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 14 May 2024 08:02:59 -0400 Subject: [PATCH 30/34] Remove 'TestAccSSMServiceSetting_disappears' -- aws_ssm_service_setting is present with 'Default' status. --- internal/service/ssm/service_setting_test.go | 23 -------------------- 1 file changed, 23 deletions(-) diff --git a/internal/service/ssm/service_setting_test.go b/internal/service/ssm/service_setting_test.go index eebd7b406b3..0700d846bce 100644 --- a/internal/service/ssm/service_setting_test.go +++ b/internal/service/ssm/service_setting_test.go @@ -53,29 +53,6 @@ func TestAccSSMServiceSetting_basic(t *testing.T) { }) } -func TestAccSSMServiceSetting_disappears(t *testing.T) { - ctx := acctest.Context(t) - var setting awstypes.ServiceSetting - resourceName := "aws_ssm_service_setting.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.SSMServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckServiceSettingDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccServiceSettingConfig_basic("false"), - Check: resource.ComposeTestCheckFunc( - testAccServiceSettingExists(ctx, resourceName, &setting), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfssm.ResourceServiceSetting(), resourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - func testAccCheckServiceSettingDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) From 3096a4e76fa072a4c5066aea9aa6c83677e34e0d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 14 May 2024 08:07:29 -0400 Subject: [PATCH 31/34] 'findMaintenanceWindowTargetByID' -> 'findMaintenanceWindowTargetByTwoPartKey'. --- internal/service/ssm/exports_test.go | 2 +- internal/service/ssm/maintenance_window_target.go | 8 +++++--- internal/service/ssm/maintenance_window_target_test.go | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/internal/service/ssm/exports_test.go b/internal/service/ssm/exports_test.go index 7f8a84ee002..d4520ed3bfb 100644 --- a/internal/service/ssm/exports_test.go +++ b/internal/service/ssm/exports_test.go @@ -24,7 +24,7 @@ var ( FindDefaultDefaultPatchBaselineIDByOperatingSystem = findDefaultDefaultPatchBaselineIDByOperatingSystem FindDocumentByName = findDocumentByName FindMaintenanceWindowByID = findMaintenanceWindowByID - FindMaintenanceWindowTargetByID = findMaintenanceWindowTargetByID + FindMaintenanceWindowTargetByTwoPartKey = findMaintenanceWindowTargetByTwoPartKey FindMaintenanceWindowTaskByTwoPartKey = findMaintenanceWindowTaskByTwoPartKey FindParameterByName = findParameterByName FindPatchBaselineByID = findPatchBaselineByID diff --git a/internal/service/ssm/maintenance_window_target.go b/internal/service/ssm/maintenance_window_target.go index e58073f258c..be037351ee2 100644 --- a/internal/service/ssm/maintenance_window_target.go +++ b/internal/service/ssm/maintenance_window_target.go @@ -137,7 +137,8 @@ func resourceMaintenanceWindowTargetRead(ctx context.Context, d *schema.Resource var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SSMClient(ctx) - target, err := findMaintenanceWindowTargetByID(ctx, conn, d.Id()) + windowID := d.Get("window_id").(string) + target, err := findMaintenanceWindowTargetByTwoPartKey(ctx, conn, windowID, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] SSM Maintenance Window Target %s not found, removing from state", d.Id()) @@ -213,14 +214,15 @@ func resourceMaintenanceWindowTargetDelete(ctx context.Context, d *schema.Resour return diags } -func findMaintenanceWindowTargetByID(ctx context.Context, conn *ssm.Client, id string) (*awstypes.MaintenanceWindowTarget, error) { +func findMaintenanceWindowTargetByTwoPartKey(ctx context.Context, conn *ssm.Client, windowID, windowTargetID string) (*awstypes.MaintenanceWindowTarget, error) { input := &ssm.DescribeMaintenanceWindowTargetsInput{ Filters: []awstypes.MaintenanceWindowFilter{ { Key: aws.String("WindowTargetId"), - Values: []string{id}, + Values: []string{windowTargetID}, }, }, + WindowId: aws.String(windowID), } return findMaintenanceWindowTarget(ctx, conn, input) diff --git a/internal/service/ssm/maintenance_window_target_test.go b/internal/service/ssm/maintenance_window_target_test.go index f8ba5391117..27522d1c643 100644 --- a/internal/service/ssm/maintenance_window_target_test.go +++ b/internal/service/ssm/maintenance_window_target_test.go @@ -271,7 +271,7 @@ func testAccCheckMaintenanceWindowTargetExists(ctx context.Context, n string, v conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) - output, err := tfssm.FindMaintenanceWindowTargetByID(ctx, conn, rs.Primary.ID) + output, err := tfssm.FindMaintenanceWindowTargetByTwoPartKey(ctx, conn, rs.Primary.Attributes["windows_id"], rs.Primary.ID) if err != nil { return err @@ -292,7 +292,7 @@ func testAccCheckMaintenanceWindowTargetDestroy(ctx context.Context) resource.Te continue } - _, err := tfssm.FindMaintenanceWindowTargetByID(ctx, conn, rs.Primary.ID) + _, err := tfssm.FindMaintenanceWindowTargetByTwoPartKey(ctx, conn, rs.Primary.Attributes["windows_id"], rs.Primary.ID) if tfresource.NotFound(err) { continue From 51595597b0d624dbad09edc93a20100daa6eac5d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 14 May 2024 08:16:15 -0400 Subject: [PATCH 32/34] Fix call to 'findMaintenanceWindowTaskByTwoPartKey'. --- internal/service/ssm/maintenance_window_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/ssm/maintenance_window_task.go b/internal/service/ssm/maintenance_window_task.go index 840ea3f0613..24acdec7895 100644 --- a/internal/service/ssm/maintenance_window_task.go +++ b/internal/service/ssm/maintenance_window_task.go @@ -383,7 +383,7 @@ func resourceMaintenanceWindowTaskRead(ctx context.Context, d *schema.ResourceDa var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SSMClient(ctx) - output, err := findMaintenanceWindowTaskByTwoPartKey(ctx, conn, d.Id(), d.Get("window_task_id").(string)) + output, err := findMaintenanceWindowTaskByTwoPartKey(ctx, conn, d.Get("window_id").(string), d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] SSM Maintenance Window Task %s not found, removing from state", d.Id()) From c4ed7546044c29c3ecea1fb2e57d3416d2395155 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 14 May 2024 09:22:38 -0400 Subject: [PATCH 33/34] Fix typo. --- internal/service/ssm/maintenance_window_target.go | 2 +- internal/service/ssm/maintenance_window_target_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/service/ssm/maintenance_window_target.go b/internal/service/ssm/maintenance_window_target.go index be037351ee2..90ce0a8350e 100644 --- a/internal/service/ssm/maintenance_window_target.go +++ b/internal/service/ssm/maintenance_window_target.go @@ -36,7 +36,7 @@ func resourceMaintenanceWindowTarget() *schema.Resource { StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { idParts := strings.Split(d.Id(), "/") if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { - return nil, fmt.Errorf("Unexpected format of ID (%q), expected WINDOW_ID/WINDOW_TARGET_ID", d.Id()) + return nil, fmt.Errorf("unexpected format of ID (%s), expected WINDOW_ID/WINDOW_TARGET_ID", d.Id()) } d.Set("window_id", idParts[0]) d.SetId(idParts[1]) diff --git a/internal/service/ssm/maintenance_window_target_test.go b/internal/service/ssm/maintenance_window_target_test.go index 27522d1c643..6e200342ec1 100644 --- a/internal/service/ssm/maintenance_window_target_test.go +++ b/internal/service/ssm/maintenance_window_target_test.go @@ -271,7 +271,7 @@ func testAccCheckMaintenanceWindowTargetExists(ctx context.Context, n string, v conn := acctest.Provider.Meta().(*conns.AWSClient).SSMClient(ctx) - output, err := tfssm.FindMaintenanceWindowTargetByTwoPartKey(ctx, conn, rs.Primary.Attributes["windows_id"], rs.Primary.ID) + output, err := tfssm.FindMaintenanceWindowTargetByTwoPartKey(ctx, conn, rs.Primary.Attributes["window_id"], rs.Primary.ID) if err != nil { return err @@ -292,7 +292,7 @@ func testAccCheckMaintenanceWindowTargetDestroy(ctx context.Context) resource.Te continue } - _, err := tfssm.FindMaintenanceWindowTargetByTwoPartKey(ctx, conn, rs.Primary.Attributes["windows_id"], rs.Primary.ID) + _, err := tfssm.FindMaintenanceWindowTargetByTwoPartKey(ctx, conn, rs.Primary.Attributes["window_id"], rs.Primary.ID) if tfresource.NotFound(err) { continue From 30e6c5f977418b5992cebda4648e07dcf94ae44e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 14 May 2024 09:40:54 -0400 Subject: [PATCH 34/34] Fix 'findMaintenanceWindowTargets'. --- internal/service/ssm/maintenance_window_target.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/service/ssm/maintenance_window_target.go b/internal/service/ssm/maintenance_window_target.go index 90ce0a8350e..23f1074a6d8 100644 --- a/internal/service/ssm/maintenance_window_target.go +++ b/internal/service/ssm/maintenance_window_target.go @@ -14,6 +14,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ssm" awstypes "github.com/aws/aws-sdk-go-v2/service/ssm/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" @@ -245,6 +246,13 @@ func findMaintenanceWindowTargets(ctx context.Context, conn *ssm.Client, input * for pages.HasMorePages() { page, err := pages.NextPage(ctx) + if errs.IsA[*awstypes.DoesNotExistException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + if err != nil { return nil, err }