diff --git a/.changelog/40594.txt b/.changelog/40594.txt new file mode 100644 index 00000000000..d90f52f927d --- /dev/null +++ b/.changelog/40594.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_cloudwatch_log_index_policy +``` \ No newline at end of file diff --git a/internal/sdkv2/schema.go b/internal/sdkv2/schema.go index 5f87af00148..c085efe764d 100644 --- a/internal/sdkv2/schema.go +++ b/internal/sdkv2/schema.go @@ -4,7 +4,10 @@ package sdkv2 import ( + "sync" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) // Adapted from https://github.com/hashicorp/terraform-provider-google/google/datasource_helpers.go. Thanks! @@ -61,3 +64,22 @@ func DataSourceSchemaFromResourceSchema(rs map[string]*schema.Schema) map[string return ds } + +// JSONDocumentSchemaRequired returns the standard schema for a required JSON document. +var JSONDocumentSchemaRequired = sync.OnceValue(jsonDocumentSchemaRequiredFunc(SuppressEquivalentJSONDocuments)) + +// IAMPolicyDocumentSchemaRequired returns the standard schema for a required IAM policy JSON document. +var IAMPolicyDocumentSchemaRequired = sync.OnceValue(jsonDocumentSchemaRequiredFunc(SuppressEquivalentIAMPolicyDocuments)) + +func jsonDocumentSchemaRequiredFunc(diffSuppressFunc schema.SchemaDiffSuppressFunc) func() *schema.Schema { + return func() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringIsJSON, + DiffSuppressFunc: diffSuppressFunc, + DiffSuppressOnRefresh: true, + StateFunc: NormalizeJsonStringSchemaStateFunc, + } + } +} diff --git a/internal/sdkv2/suppress.go b/internal/sdkv2/suppress.go index 97b1a813a86..276159166ba 100644 --- a/internal/sdkv2/suppress.go +++ b/internal/sdkv2/suppress.go @@ -6,7 +6,9 @@ package sdkv2 import ( "strings" + awspolicy "github.com/hashicorp/awspolicyequivalence" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/json" ) // SuppressEquivalentStringCaseInsensitive provides custom difference suppression @@ -14,3 +16,29 @@ import ( func SuppressEquivalentStringCaseInsensitive(k, old, new string, d *schema.ResourceData) bool { return strings.EqualFold(old, new) } + +// SuppressEquivalentJSONDocuments provides custom difference suppression +// for JSON documents in the given strings that are equivalent. +func SuppressEquivalentJSONDocuments(k, old, new string, d *schema.ResourceData) bool { + return json.EqualStrings(old, new) +} + +// SuppressEquivalentIAMPolicyDocuments provides custom difference suppression +// for IAM policy documents in the given strings that are equivalent. +func SuppressEquivalentIAMPolicyDocuments(k, old, new string, d *schema.ResourceData) bool { + if equalEmptyJSONStrings(old, new) { + return true + } + + equivalent, err := awspolicy.PoliciesAreEquivalent(old, new) + if err != nil { + return false + } + + return equivalent +} + +func equalEmptyJSONStrings(old, new string) bool { + old, new = strings.TrimSpace(old), strings.TrimSpace(new) + return (old == "" || old == "{}") && (new == "" || new == "{}") +} diff --git a/internal/service/logs/account_policy.go b/internal/service/logs/account_policy.go index ddfdb47a911..1e7f9ff5fe6 100644 --- a/internal/service/logs/account_policy.go +++ b/internal/service/logs/account_policy.go @@ -11,7 +11,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/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" @@ -20,6 +20,7 @@ import ( "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/sdkv2" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" @@ -38,17 +39,7 @@ func resourceAccountPolicy() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "policy_document": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validAccountPolicyDocument, - DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, - DiffSuppressOnRefresh: true, - StateFunc: func(v interface{}) string { - json, _ := structure.NormalizeJsonString(v) - return json - }, - }, + "policy_document": sdkv2.JSONDocumentSchemaRequired(), "policy_name": { Type: schema.TypeString, Required: true, @@ -58,13 +49,13 @@ func resourceAccountPolicy() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateDiagFunc: enum.Validate[types.PolicyType](), + ValidateDiagFunc: enum.Validate[awstypes.PolicyType](), }, names.AttrScope: { Type: schema.TypeString, Optional: true, - Default: types.ScopeAll, - ValidateDiagFunc: enum.Validate[types.Scope](), + Default: awstypes.ScopeAll, + ValidateDiagFunc: enum.Validate[awstypes.Scope](), }, "selection_criteria": { Type: schema.TypeString, @@ -88,8 +79,8 @@ func resourceAccountPolicyPut(ctx context.Context, d *schema.ResourceData, meta input := &cloudwatchlogs.PutAccountPolicyInput{ PolicyDocument: aws.String(policy), PolicyName: aws.String(name), - PolicyType: types.PolicyType(d.Get("policy_type").(string)), - Scope: types.Scope(d.Get(names.AttrScope).(string)), + PolicyType: awstypes.PolicyType(d.Get("policy_type").(string)), + Scope: awstypes.Scope(d.Get(names.AttrScope).(string)), } if v, ok := d.GetOk("selection_criteria"); ok { @@ -99,7 +90,7 @@ func resourceAccountPolicyPut(ctx context.Context, d *schema.ResourceData, meta output, err := conn.PutAccountPolicy(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "creating CloudWatch Logs Account Policy (%s): %s", name, err) + return sdkdiag.AppendErrorf(diags, "putting CloudWatch Logs Account Policy (%s): %s", name, err) } d.SetId(aws.ToString(output.AccountPolicy.PolicyName)) @@ -111,7 +102,7 @@ func resourceAccountPolicyRead(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).LogsClient(ctx) - output, err := findAccountPolicyByTwoPartKey(ctx, conn, types.PolicyType(d.Get("policy_type").(string)), d.Id()) + output, err := findAccountPolicyByTwoPartKey(ctx, conn, awstypes.PolicyType(d.Get("policy_type").(string)), d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] CloudWatch Logs Account Policy (%s) not found, removing from state", d.Id()) @@ -149,10 +140,10 @@ func resourceAccountPolicyDelete(ctx context.Context, d *schema.ResourceData, me log.Printf("[DEBUG] Deleting CloudWatch Logs Account Policy: %s", d.Id()) _, err := conn.DeleteAccountPolicy(ctx, &cloudwatchlogs.DeleteAccountPolicyInput{ PolicyName: aws.String(d.Id()), - PolicyType: types.PolicyType(d.Get("policy_type").(string)), + PolicyType: awstypes.PolicyType(d.Get("policy_type").(string)), }) - if errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return diags } @@ -178,15 +169,48 @@ func resourceAccountPolicyImport(d *schema.ResourceData, meta interface{}) ([]*s return []*schema.ResourceData{d}, nil } -func findAccountPolicyByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, policyType types.PolicyType, policyName string) (*types.AccountPolicy, error) { - input := &cloudwatchlogs.DescribeAccountPoliciesInput{ +func findAccountPolicyByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, policyType awstypes.PolicyType, policyName string) (*awstypes.AccountPolicy, error) { + input := cloudwatchlogs.DescribeAccountPoliciesInput{ PolicyName: aws.String(policyName), PolicyType: policyType, } + output, err := findAccountPolicy(ctx, conn, &input) - output, err := conn.DescribeAccountPolicies(ctx, input) + if err != nil { + return nil, err + } + + if output.PolicyDocument == nil { + return nil, tfresource.NewEmptyResultError(input) + } - if errs.IsA[*types.ResourceNotFoundException](err) { + return output, err +} + +func findAccountPolicy(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeAccountPoliciesInput) (*awstypes.AccountPolicy, error) { + output, err := findAccountPolicies(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findAccountPolicies(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeAccountPoliciesInput) ([]awstypes.AccountPolicy, error) { + var output []awstypes.AccountPolicy + + err := describeAccountPoliciesPages(ctx, conn, input, func(page *cloudwatchlogs.DescribeAccountPoliciesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + output = append(output, page.AccountPolicies...) + + return !lastPage + }) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, @@ -197,9 +221,5 @@ func findAccountPolicyByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Cli return nil, err } - if output == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return tfresource.AssertSingleValueResult(output.AccountPolicies) + return output, nil } diff --git a/internal/service/logs/account_policy_test.go b/internal/service/logs/account_policy_test.go index db41081bb1e..381ee3dd009 100644 --- a/internal/service/logs/account_policy_test.go +++ b/internal/service/logs/account_policy_test.go @@ -185,21 +185,6 @@ func testAccCheckAccountPolicyExists(ctx context.Context, n string, v *types.Acc } } -func testAccAccountPolicyImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { - return func(s *terraform.State) (string, error) { - rs, ok := s.RootModule().Resources[resourceName] - if !ok { - return "", fmt.Errorf("Not found: %s", resourceName) - } - - policyName := rs.Primary.ID - policyType := rs.Primary.Attributes["policy_type"] - stateID := fmt.Sprintf("%s:%s", policyName, policyType) - - return stateID, nil - } -} - func testAccCheckAccountPolicyDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).LogsClient(ctx) @@ -219,13 +204,28 @@ func testAccCheckAccountPolicyDestroy(ctx context.Context) resource.TestCheckFun return err } - return fmt.Errorf("CloudWatch Logs Resource Policy still exists: %s", rs.Primary.ID) + return fmt.Errorf("CloudWatch Logs Account Policy still exists: %s", rs.Primary.ID) } return nil } } +func testAccAccountPolicyImportStateIDFunc(n string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[n] + if !ok { + return "", fmt.Errorf("Not found: %s", n) + } + + policyName := rs.Primary.ID + policyType := rs.Primary.Attributes["policy_type"] + stateID := fmt.Sprintf("%s:%s", policyName, policyType) + + return stateID, nil + } +} + func testAccCheckAccountHasSubscriptionFilterPolicy(ctx context.Context, resourceName string, rName string) resource.TestCheckFunc { return func(s *terraform.State) error { expectedJSONTemplate := `{ diff --git a/internal/service/logs/anomaly_detector.go b/internal/service/logs/anomaly_detector.go index 788672e044f..3ea03d937b3 100644 --- a/internal/service/logs/anomaly_detector.go +++ b/internal/service/logs/anomaly_detector.go @@ -5,7 +5,7 @@ package logs import ( "context" - "errors" + "fmt" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" @@ -20,11 +20,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/framework" - "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -37,33 +36,23 @@ import ( // @Testing(importStateIdAttribute="arn") // @Testing(importIgnore="enabled") // @Testing(existsType="github.com/aws/aws-sdk-go-v2/service/logs;cloudwatchlogs.GetLogAnomalyDetectorOutput") -func newResourceAnomalyDetector(_ context.Context) (resource.ResourceWithConfigure, error) { - r := &resourceAnomalyDetector{} +func newAnomalyDetectorResource(context.Context) (resource.ResourceWithConfigure, error) { + r := &anomalyDetectorResource{} return r, nil } -const ( - ResNameAnomalyDetector = "Anomaly Detector" -) - -type resourceAnomalyDetector struct { +type anomalyDetectorResource struct { framework.ResourceWithConfigure } -func (r *resourceAnomalyDetector) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "aws_cloudwatch_log_anomaly_detector" +func (*anomalyDetectorResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_cloudwatch_log_anomaly_detector" } -func (r *resourceAnomalyDetector) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ +func (r *anomalyDetectorResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - names.AttrARN: framework.ARNAttributeComputedOnly(), - "log_group_arn_list": schema.ListAttribute{ - CustomType: fwtypes.ListOfStringType, - ElementType: types.StringType, - Required: true, - }, "anomaly_visibility_time": schema.Int64Attribute{ Optional: true, Computed: true, @@ -74,9 +63,13 @@ func (r *resourceAnomalyDetector) Schema(ctx context.Context, req resource.Schem int64planmodifier.UseStateForUnknown(), }, }, + names.AttrARN: framework.ARNAttributeComputedOnly(), "detector_name": schema.StringAttribute{ Optional: true, }, + names.AttrEnabled: schema.BoolAttribute{ + Required: true, + }, "evaluation_frequency": schema.StringAttribute{ CustomType: fwtypes.StringEnumType[awstypes.EvaluationFrequency](), Optional: true, @@ -84,219 +77,206 @@ func (r *resourceAnomalyDetector) Schema(ctx context.Context, req resource.Schem "filter_pattern": schema.StringAttribute{ Optional: true, }, - names.AttrEnabled: schema.BoolAttribute{ - Required: true, - }, names.AttrKMSKeyID: schema.StringAttribute{ Optional: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), - stringplanmodifier.UseStateForUnknown(), }, }, + "log_group_arn_list": schema.ListAttribute{ + CustomType: fwtypes.ListOfARNType, + Required: true, + ElementType: types.StringType, + }, names.AttrTags: tftags.TagsAttribute(), names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), }, } } -func (r *resourceAnomalyDetector) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - conn := r.Meta().LogsClient(ctx) - - var plan resourceAnomalyDetectorData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { +func (r *anomalyDetectorResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data anomalyDetectorResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &cloudwatchlogs.CreateLogAnomalyDetectorInput{ - Tags: getTagsIn(ctx), - } - resp.Diagnostics.Append(flex.Expand(ctx, plan, in)...) + conn := r.Meta().LogsClient(ctx) - if resp.Diagnostics.HasError() { + input := &cloudwatchlogs.CreateLogAnomalyDetectorInput{} + response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) + if response.Diagnostics.HasError() { return } - out, err := conn.CreateLogAnomalyDetector(ctx, in) + // Additional fields. + input.Tags = getTagsIn(ctx) + + outputCLAD, err := conn.CreateLogAnomalyDetector(ctx, input) + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Logs, create.ErrActionCreating, ResNameAnomalyDetector, plan.ARN.String(), err), - err.Error(), - ) - return - } + response.Diagnostics.AddError("creating CloudWatch Logs Anomaly Detector", err.Error()) - if out == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Logs, create.ErrActionCreating, ResNameAnomalyDetector, plan.ARN.String(), nil), - errors.New("empty output").Error(), - ) return } - resp.Diagnostics.Append(flex.Flatten(ctx, out, &plan)...) - if resp.Diagnostics.HasError() { + // Set values for unknowns. + data.AnomalyDetectorARN = fwflex.StringToFramework(ctx, outputCLAD.AnomalyDetectorArn) + + outputGLAD, err := findLogAnomalyDetectorByARN(ctx, conn, data.AnomalyDetectorARN.ValueString()) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("reading CloudWatch Logs Anomaly Detector (%s)", data.AnomalyDetectorARN.ValueString()), err.Error()) + return } - plan.ARN = flex.StringToFramework(ctx, out.AnomalyDetectorArn) + data.AnomalyVisibilityTime = fwflex.Int64ToFramework(ctx, outputGLAD.AnomalyVisibilityTime) - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *resourceAnomalyDetector) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - conn := r.Meta().LogsClient(ctx) - - var state resourceAnomalyDetectorData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *anomalyDetectorResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data anomalyDetectorResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - out, err := findLogAnomalyDetectorByARN(ctx, conn, state.ARN.ValueString()) + conn := r.Meta().LogsClient(ctx) + + output, err := findLogAnomalyDetectorByARN(ctx, conn, data.AnomalyDetectorARN.ValueString()) + if tfresource.NotFound(err) { - resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) - resp.State.RemoveResource(ctx) + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + return } + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Logs, create.ErrActionSetting, ResNameAnomalyDetector, state.ARN.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading CloudWatch Logs Anomaly Detector (%s)", data.AnomalyDetectorARN.ValueString()), err.Error()) + return } - resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...) - if resp.Diagnostics.HasError() { + response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) + if response.Diagnostics.HasError() { return } - state.AnomalyVisibilityTime = flex.Int64ToFramework(ctx, out.AnomalyVisibilityTime) - - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *resourceAnomalyDetector) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - conn := r.Meta().LogsClient(ctx) - - var plan, state resourceAnomalyDetectorData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *anomalyDetectorResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new anomalyDetectorResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { + return + } + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { return } - diff, d := flex.Calculate(ctx, plan, state) - resp.Diagnostics.Append(d...) - if resp.Diagnostics.HasError() { + conn := r.Meta().LogsClient(ctx) + + diff, d := fwflex.Calculate(ctx, new, old) + response.Diagnostics.Append(d...) + if response.Diagnostics.HasError() { return } if diff.HasChanges() { - in := &cloudwatchlogs.UpdateLogAnomalyDetectorInput{} + input := &cloudwatchlogs.UpdateLogAnomalyDetectorInput{} - resp.Diagnostics.Append(flex.Expand(ctx, plan, in)...) - if resp.Diagnostics.HasError() { + response.Diagnostics.Append(fwflex.Expand(ctx, new, input)...) + if response.Diagnostics.HasError() { return } - in.AnomalyDetectorArn = plan.ARN.ValueStringPointer() - in.AnomalyVisibilityTime = plan.AnomalyVisibilityTime.ValueInt64Pointer() + _, err := conn.UpdateLogAnomalyDetector(ctx, input) - out, err := conn.UpdateLogAnomalyDetector(ctx, in) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Logs, create.ErrActionUpdating, ResNameAnomalyDetector, plan.ARN.String(), err), - err.Error(), - ) - return - } - if out == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Logs, create.ErrActionUpdating, ResNameAnomalyDetector, plan.ARN.String(), nil), - errors.New("empty output").Error(), - ) - return - } + response.Diagnostics.AddError(fmt.Sprintf("updating CloudWatch Logs Anomaly Detector (%s)", new.AnomalyDetectorARN.ValueString()), err.Error()) - resp.Diagnostics.Append(flex.Flatten(ctx, out, &plan)...) - if resp.Diagnostics.HasError() { return } } - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + response.Diagnostics.Append(response.State.Set(ctx, &new)...) } -func (r *resourceAnomalyDetector) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - conn := r.Meta().LogsClient(ctx) - - var state resourceAnomalyDetectorData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *anomalyDetectorResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data anomalyDetectorResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &cloudwatchlogs.DeleteLogAnomalyDetectorInput{ - AnomalyDetectorArn: state.ARN.ValueStringPointer(), + conn := r.Meta().LogsClient(ctx) + + _, err := conn.DeleteLogAnomalyDetector(ctx, &cloudwatchlogs.DeleteLogAnomalyDetectorInput{ + AnomalyDetectorArn: data.AnomalyDetectorARN.ValueStringPointer(), + }) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return } - _, err := conn.DeleteLogAnomalyDetector(ctx, in) if err != nil { - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return - } - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Logs, create.ErrActionDeleting, ResNameAnomalyDetector, state.ARN.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("deleting CloudWatch Logs Anomaly Detector (%s)", data.AnomalyDetectorARN.ValueString()), err.Error()) + return } } -func (r *resourceAnomalyDetector) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root(names.AttrARN), req, resp) +func (r *anomalyDetectorResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root(names.AttrARN), request, response) } -func (r *resourceAnomalyDetector) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { - r.SetTagsAll(ctx, req, resp) +func (r *anomalyDetectorResource) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, request, response) } func findLogAnomalyDetectorByARN(ctx context.Context, conn *cloudwatchlogs.Client, arn string) (*cloudwatchlogs.GetLogAnomalyDetectorOutput, error) { - in := &cloudwatchlogs.GetLogAnomalyDetectorInput{ + input := cloudwatchlogs.GetLogAnomalyDetectorInput{ AnomalyDetectorArn: aws.String(arn), } - out, err := conn.GetLogAnomalyDetector(ctx, in) - if err != nil { - if errs.IsA[*awstypes.ResourceNotFoundException](err) || errs.IsA[*awstypes.AccessDeniedException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } + return findLogAnomalyDetector(ctx, conn, &input) +} + +func findLogAnomalyDetector(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.GetLogAnomalyDetectorInput) (*cloudwatchlogs.GetLogAnomalyDetectorOutput, error) { + output, err := conn.GetLogAnomalyDetector(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) || errs.IsA[*awstypes.AccessDeniedException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, } + } + if err != nil { return nil, err } - if out == nil { - return nil, tfresource.NewEmptyResultError(in) + if output == nil { + return nil, tfresource.NewEmptyResultError(input) } - return out, nil + return output, nil } -type resourceAnomalyDetectorData struct { - ARN types.String `tfsdk:"arn"` - LogGroupARNList fwtypes.ListValueOf[types.String] `tfsdk:"log_group_arn_list"` +type anomalyDetectorResourceModel struct { + AnomalyDetectorARN types.String `tfsdk:"arn"` AnomalyVisibilityTime types.Int64 `tfsdk:"anomaly_visibility_time"` DetectorName types.String `tfsdk:"detector_name"` Enabled types.Bool `tfsdk:"enabled"` EvaluationFrequency fwtypes.StringEnum[awstypes.EvaluationFrequency] `tfsdk:"evaluation_frequency"` FilterPattern types.String `tfsdk:"filter_pattern"` KMSKeyID types.String `tfsdk:"kms_key_id"` + LogGroupARNList fwtypes.ListOfARN `tfsdk:"log_group_arn_list"` Tags tftags.Map `tfsdk:"tags"` TagsAll tftags.Map `tfsdk:"tags_all"` } diff --git a/internal/service/logs/anomaly_detector_test.go b/internal/service/logs/anomaly_detector_test.go index 1ea32a48a18..8a694fce8d2 100644 --- a/internal/service/logs/anomaly_detector_test.go +++ b/internal/service/logs/anomaly_detector_test.go @@ -5,7 +5,6 @@ package logs_test import ( "context" - "errors" "fmt" "testing" @@ -15,7 +14,6 @@ import ( "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" tflogs "github.com/hashicorp/terraform-provider-aws/internal/service/logs" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" @@ -23,11 +21,7 @@ import ( func TestAccLogsAnomalyDetector_basic(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var loganomalydetector cloudwatchlogs.GetLogAnomalyDetectorOutput + var v cloudwatchlogs.GetLogAnomalyDetectorOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_cloudwatch_log_anomaly_detector.test" @@ -42,7 +36,7 @@ func TestAccLogsAnomalyDetector_basic(t *testing.T) { { Config: testAccLogAnomalyDetectorConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAnomalyDetectorExists(ctx, resourceName, &loganomalydetector), + testAccCheckAnomalyDetectorExists(ctx, resourceName, &v), resource.TestCheckResourceAttrSet(resourceName, "detector_name"), resource.TestCheckResourceAttr(resourceName, "evaluation_frequency", "TEN_MIN"), resource.TestCheckResourceAttr(resourceName, "anomaly_visibility_time", "7"), @@ -63,11 +57,7 @@ func TestAccLogsAnomalyDetector_basic(t *testing.T) { func TestAccLogsAnomalyDetector_update(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var loganomalydetector cloudwatchlogs.GetLogAnomalyDetectorOutput + var v cloudwatchlogs.GetLogAnomalyDetectorOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_cloudwatch_log_anomaly_detector.test" @@ -82,7 +72,7 @@ func TestAccLogsAnomalyDetector_update(t *testing.T) { { Config: testAccLogAnomalyDetectorConfig_update(rName, "TEN_MIN", acctest.CtFalse, 7), Check: resource.ComposeTestCheckFunc( - testAccCheckAnomalyDetectorExists(ctx, resourceName, &loganomalydetector), + testAccCheckAnomalyDetectorExists(ctx, resourceName, &v), resource.TestCheckResourceAttrSet(resourceName, "detector_name"), resource.TestCheckResourceAttr(resourceName, "evaluation_frequency", "TEN_MIN"), resource.TestCheckResourceAttr(resourceName, "anomaly_visibility_time", "7"), @@ -102,7 +92,7 @@ func TestAccLogsAnomalyDetector_update(t *testing.T) { { Config: testAccLogAnomalyDetectorConfig_update(rName, "FIVE_MIN", acctest.CtTrue, 8), Check: resource.ComposeTestCheckFunc( - testAccCheckAnomalyDetectorExists(ctx, resourceName, &loganomalydetector), + testAccCheckAnomalyDetectorExists(ctx, resourceName, &v), resource.TestCheckResourceAttrSet(resourceName, "detector_name"), resource.TestCheckResourceAttr(resourceName, "evaluation_frequency", "FIVE_MIN"), resource.TestCheckResourceAttr(resourceName, "anomaly_visibility_time", "8"), @@ -124,11 +114,7 @@ func TestAccLogsAnomalyDetector_update(t *testing.T) { func TestAccLogsAnomalyDetector_disappears(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var loganomalydetector cloudwatchlogs.GetLogAnomalyDetectorOutput + var v cloudwatchlogs.GetLogAnomalyDetectorOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_cloudwatch_log_anomaly_detector.test" @@ -143,7 +129,7 @@ func TestAccLogsAnomalyDetector_disappears(t *testing.T) { { Config: testAccLogAnomalyDetectorConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckAnomalyDetectorExists(ctx, resourceName, &loganomalydetector), + testAccCheckAnomalyDetectorExists(ctx, resourceName, &v), acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tflogs.ResourceAnomalyDetector, resourceName), ), ExpectNonEmptyPlan: true, @@ -162,6 +148,7 @@ func testAccCheckAnomalyDetectorDestroy(ctx context.Context) resource.TestCheckF } _, err := tflogs.FindLogAnomalyDetectorByARN(ctx, conn, rs.Primary.Attributes[names.AttrARN]) + if tfresource.NotFound(err) { continue } @@ -170,43 +157,39 @@ func testAccCheckAnomalyDetectorDestroy(ctx context.Context) resource.TestCheckF return err } - return fmt.Errorf("CloudwatchLogs Anomaly Detector %s still exists", rs.Primary.Attributes[names.AttrARN]) + return fmt.Errorf("CloudWatch Logs Anomaly Detector still exists: %s", rs.Primary.Attributes[names.AttrARN]) } return nil } } -func testAccCheckAnomalyDetectorExists(ctx context.Context, name string, loganomalydetector *cloudwatchlogs.GetLogAnomalyDetectorOutput) resource.TestCheckFunc { +func testAccCheckAnomalyDetectorExists(ctx context.Context, n string, v *cloudwatchlogs.GetLogAnomalyDetectorOutput) 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.Logs, create.ErrActionCheckingExistence, tflogs.ResNameAnomalyDetector, name, errors.New("not found")) - } - - if rs.Primary.ID == "" { - return create.Error(names.Logs, create.ErrActionCheckingExistence, tflogs.ResNameAnomalyDetector, name, errors.New("not set")) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).LogsClient(ctx) - resp, err := tflogs.FindLogAnomalyDetectorByARN(ctx, conn, rs.Primary.Attributes[names.AttrARN]) + output, err := tflogs.FindLogAnomalyDetectorByARN(ctx, conn, rs.Primary.Attributes[names.AttrARN]) if err != nil { - return create.Error(names.Logs, create.ErrActionCheckingExistence, tflogs.ResNameAnomalyDetector, rs.Primary.Attributes[names.AttrARN], err) + return err } - *loganomalydetector = *resp + *v = *output return nil } } -func testAccAnomalyDetectorImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { +func testAccAnomalyDetectorImportStateIDFunc(n string) resource.ImportStateIdFunc { return func(s *terraform.State) (string, error) { - rs, ok := s.RootModule().Resources[resourceName] + rs, ok := s.RootModule().Resources[n] if !ok { - return "", fmt.Errorf("Not found: %s", resourceName) + return "", fmt.Errorf("Not found: %s", n) } return rs.Primary.Attributes[names.AttrARN], nil diff --git a/internal/service/logs/arn.go b/internal/service/logs/arn.go index 77e238ca8dd..b9507c6890e 100644 --- a/internal/service/logs/arn.go +++ b/internal/service/logs/arn.go @@ -11,7 +11,7 @@ const ( logGroupARNWildcardSuffix = ":*" ) -// TrimLogGroupARNWildcardSuffix trims any wilcard suffix from a Log Group ARN. -func TrimLogGroupARNWildcardSuffix(arn string) string { +// trimLogGroupARNWildcardSuffix trims any wilcard suffix from a Log Group ARN. +func trimLogGroupARNWildcardSuffix(arn string) string { return strings.TrimSuffix(arn, logGroupARNWildcardSuffix) } diff --git a/internal/service/logs/data_protection_policy.go b/internal/service/logs/data_protection_policy.go index 2b41f342164..5b530e90b46 100644 --- a/internal/service/logs/data_protection_policy.go +++ b/internal/service/logs/data_protection_policy.go @@ -5,25 +5,25 @@ package logs import ( "context" - "errors" "log" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/sdkv2" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_cloudwatch_log_data_protection_policy") +// @SDKResource("aws_cloudwatch_log_data_protection_policy", name="Data Protection Policy") func resourceDataProtectionPolicy() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceDataProtectionPolicyPut, @@ -42,34 +42,21 @@ func resourceDataProtectionPolicy() *schema.Resource { ForceNew: true, ValidateFunc: validLogGroupName, }, - "policy_document": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsJSON, - DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, - DiffSuppressOnRefresh: true, - StateFunc: func(v interface{}) string { - json, _ := structure.NormalizeJsonString(v) - return json - }, - }, + "policy_document": sdkv2.JSONDocumentSchemaRequired(), }, } } func resourceDataProtectionPolicyPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) - logGroupName := d.Get(names.AttrLogGroupName).(string) - policy, err := structure.NormalizeJsonString(d.Get("policy_document").(string)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "policy (%s) is invalid JSON: %s", policy, err) + return sdkdiag.AppendFromErr(diags, err) } + logGroupName := d.Get(names.AttrLogGroupName).(string) input := &cloudwatchlogs.PutDataProtectionPolicyInput{ LogGroupIdentifier: aws.String(logGroupName), PolicyDocument: aws.String(policy), @@ -90,10 +77,9 @@ func resourceDataProtectionPolicyPut(ctx context.Context, d *schema.ResourceData func resourceDataProtectionPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) - output, err := FindDataProtectionPolicyByID(ctx, conn, d.Id()) + output, err := findDataProtectionPolicyByLogGroupName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] CloudWatch Logs Data Protection Policy (%s) not found, removing from state", d.Id()) @@ -105,20 +91,17 @@ func resourceDataProtectionPolicyRead(ctx context.Context, d *schema.ResourceDat return sdkdiag.AppendErrorf(diags, "reading CloudWatch Logs Data Protection Policy (%s): %s", d.Id(), err) } - d.Set(names.AttrLogGroupName, output.LogGroupIdentifier) - policyToSet, err := verify.SecondJSONUnlessEquivalent(d.Get("policy_document").(string), aws.ToString(output.PolicyDocument)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "while setting policy (%s), encountered: %s", policyToSet, err) + return sdkdiag.AppendFromErr(diags, err) } policyToSet, err = structure.NormalizeJsonString(policyToSet) - if err != nil { - return sdkdiag.AppendErrorf(diags, "policy (%s) is invalid JSON: %s", policyToSet, err) + return sdkdiag.AppendFromErr(diags, err) } + d.Set(names.AttrLogGroupName, output.LogGroupIdentifier) d.Set("policy_document", policyToSet) return diags @@ -126,7 +109,6 @@ func resourceDataProtectionPolicyRead(ctx context.Context, d *schema.ResourceDat func resourceDataProtectionPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) log.Printf("[DEBUG] Deleting CloudWatch Logs Data Protection Policy: %s", d.Id()) @@ -134,7 +116,7 @@ func resourceDataProtectionPolicyDelete(ctx context.Context, d *schema.ResourceD LogGroupIdentifier: aws.String(d.Id()), }) - if nfe := (*types.ResourceNotFoundException)(nil); errors.As(err, &nfe) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return diags } @@ -145,16 +127,29 @@ func resourceDataProtectionPolicyDelete(ctx context.Context, d *schema.ResourceD return diags } -func FindDataProtectionPolicyByID(ctx context.Context, conn *cloudwatchlogs.Client, id string) (*cloudwatchlogs.GetDataProtectionPolicyOutput, error) { - input := &cloudwatchlogs.GetDataProtectionPolicyInput{ - LogGroupIdentifier: aws.String(id), +func findDataProtectionPolicyByLogGroupName(ctx context.Context, conn *cloudwatchlogs.Client, name string) (*cloudwatchlogs.GetDataProtectionPolicyOutput, error) { + input := cloudwatchlogs.GetDataProtectionPolicyInput{ + LogGroupIdentifier: aws.String(name), + } + output, err := findDataProtectionPolicy(ctx, conn, &input) + + if err != nil { + return nil, err } + if output.PolicyDocument == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, err +} + +func findDataProtectionPolicy(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.GetDataProtectionPolicyInput) (*cloudwatchlogs.GetDataProtectionPolicyOutput, error) { output, err := conn.GetDataProtectionPolicy(ctx, input) - if nfe := (*types.ResourceNotFoundException)(nil); errors.As(err, &nfe) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return nil, &retry.NotFoundError{ - LastError: nfe, + LastError: err, LastRequest: input, } } diff --git a/internal/service/logs/data_protection_policy_test.go b/internal/service/logs/data_protection_policy_test.go index f487faa0ee3..d59d1158ada 100644 --- a/internal/service/logs/data_protection_policy_test.go +++ b/internal/service/logs/data_protection_policy_test.go @@ -221,7 +221,7 @@ func testAccCheckDataProtectionPolicyDestroy(ctx context.Context) resource.TestC continue } - _, err := tflogs.FindDataProtectionPolicyByID(ctx, conn, rs.Primary.ID) + _, err := tflogs.FindDataProtectionPolicyByLogGroupName(ctx, conn, rs.Primary.ID) if tfresource.NotFound(err) { continue @@ -245,13 +245,9 @@ func testAccCheckDataProtectionPolicyExists(ctx context.Context, n string, v *cl return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No CloudWatch Logs Data Protection Policy ID is set") - } - conn := acctest.Provider.Meta().(*conns.AWSClient).LogsClient(ctx) - output, err := tflogs.FindDataProtectionPolicyByID(ctx, conn, rs.Primary.ID) + output, err := tflogs.FindDataProtectionPolicyByLogGroupName(ctx, conn, rs.Primary.ID) if err != nil { return err diff --git a/internal/service/logs/destination.go b/internal/service/logs/destination.go index c27ff37162e..95a8825a361 100644 --- a/internal/service/logs/destination.go +++ b/internal/service/logs/destination.go @@ -11,13 +11,14 @@ import ( "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/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" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -76,7 +77,6 @@ const ( func resourceDestinationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) name := d.Get(names.AttrName).(string) @@ -86,7 +86,7 @@ func resourceDestinationCreate(ctx context.Context, d *schema.ResourceData, meta TargetArn: aws.String(d.Get(names.AttrTargetARN).(string)), } - outputRaw, err := tfresource.RetryWhenIsA[*types.InvalidParameterException](ctx, propagationTimeout, func() (interface{}, error) { + outputRaw, err := tfresource.RetryWhenIsA[*awstypes.InvalidParameterException](ctx, propagationTimeout, func() (interface{}, error) { return conn.PutDestination(ctx, input) }) @@ -108,7 +108,6 @@ func resourceDestinationCreate(ctx context.Context, d *schema.ResourceData, meta func resourceDestinationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) destination, err := findDestinationByName(ctx, conn, d.Id()) @@ -133,7 +132,6 @@ func resourceDestinationRead(ctx context.Context, d *schema.ResourceData, meta i func resourceDestinationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) if d.HasChangesExcept(names.AttrTags, names.AttrTagsAll) { @@ -143,7 +141,7 @@ func resourceDestinationUpdate(ctx context.Context, d *schema.ResourceData, meta TargetArn: aws.String(d.Get(names.AttrTargetARN).(string)), } - _, err := tfresource.RetryWhenIsA[*types.InvalidParameterException](ctx, propagationTimeout, func() (interface{}, error) { + _, err := tfresource.RetryWhenIsA[*awstypes.InvalidParameterException](ctx, propagationTimeout, func() (interface{}, error) { return conn.PutDestination(ctx, input) }) @@ -157,7 +155,6 @@ func resourceDestinationUpdate(ctx context.Context, d *schema.ResourceData, meta func resourceDestinationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) log.Printf("[INFO] Deleting CloudWatch Logs Destination: %s", d.Id()) @@ -165,7 +162,7 @@ func resourceDestinationDelete(ctx context.Context, d *schema.ResourceData, meta DestinationName: aws.String(d.Id()), }) - if errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return diags } @@ -176,11 +173,29 @@ func resourceDestinationDelete(ctx context.Context, d *schema.ResourceData, meta return diags } -func findDestinationByName(ctx context.Context, conn *cloudwatchlogs.Client, name string) (*types.Destination, error) { - input := &cloudwatchlogs.DescribeDestinationsInput{ +func findDestinationByName(ctx context.Context, conn *cloudwatchlogs.Client, name string) (*awstypes.Destination, error) { + input := cloudwatchlogs.DescribeDestinationsInput{ DestinationNamePrefix: aws.String(name), } + return findDestination(ctx, conn, &input, func(v *awstypes.Destination) bool { + return aws.ToString(v.DestinationName) == name + }) +} + +func findDestination(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeDestinationsInput, filter tfslices.Predicate[*awstypes.Destination]) (*awstypes.Destination, error) { + output, err := findDestinations(ctx, conn, input, filter) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findDestinations(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeDestinationsInput, filter tfslices.Predicate[*awstypes.Destination]) ([]awstypes.Destination, error) { + var output []awstypes.Destination + pages := cloudwatchlogs.NewDescribeDestinationsPaginator(conn, input) for pages.HasMorePages() { page, err := pages.NextPage(ctx) @@ -190,11 +205,11 @@ func findDestinationByName(ctx context.Context, conn *cloudwatchlogs.Client, nam } for _, v := range page.Destinations { - if aws.ToString(v.DestinationName) == name { - return &v, nil + if filter(&v) { + output = append(output, v) } } } - return nil, tfresource.NewEmptyResultError(input) + return output, nil } diff --git a/internal/service/logs/destination_policy.go b/internal/service/logs/destination_policy.go index 84e65546bfc..6adf0a6344d 100644 --- a/internal/service/logs/destination_policy.go +++ b/internal/service/logs/destination_policy.go @@ -9,17 +9,18 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/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/structure" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/sdkv2" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) -// @SDKResource("aws_cloudwatch_log_destination_policy") +// @SDKResource("aws_cloudwatch_log_destination_policy", name="Destination Policy") func resourceDestinationPolicy() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceDestinationPolicyPut, @@ -32,16 +33,7 @@ func resourceDestinationPolicy() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "access_policy": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringIsJSON, - DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, - StateFunc: func(v interface{}) string { - json, _ := structure.NormalizeJsonString(v) - return json - }, - }, + "access_policy": sdkv2.IAMPolicyDocumentSchemaRequired(), "destination_name": { Type: schema.TypeString, Required: true, @@ -57,12 +49,16 @@ func resourceDestinationPolicy() *schema.Resource { func resourceDestinationPolicyPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) + policy, err := structure.NormalizeJsonString(d.Get("access_policy").(string)) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + name := d.Get("destination_name").(string) input := &cloudwatchlogs.PutDestinationPolicyInput{ - AccessPolicy: aws.String(d.Get("access_policy").(string)), + AccessPolicy: aws.String(policy), DestinationName: aws.String(name), } @@ -70,7 +66,7 @@ func resourceDestinationPolicyPut(ctx context.Context, d *schema.ResourceData, m input.ForceUpdate = aws.Bool(v.(bool)) } - _, err := conn.PutDestinationPolicy(ctx, input) + _, err = conn.PutDestinationPolicy(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "putting CloudWatch Logs Destination Policy (%s): %s", name, err) @@ -85,10 +81,9 @@ func resourceDestinationPolicyPut(ctx context.Context, d *schema.ResourceData, m func resourceDestinationPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) - destination, err := findDestinationByName(ctx, conn, d.Id()) + destination, err := findDestinationPolicyByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] CloudWatch Logs Destination Policy (%s) not found, removing from state", d.Id()) @@ -100,8 +95,32 @@ func resourceDestinationPolicyRead(ctx context.Context, d *schema.ResourceData, return sdkdiag.AppendErrorf(diags, "reading CloudWatch Logs Destination Policy (%s): %s", d.Id(), err) } - d.Set("access_policy", destination.AccessPolicy) + policyToSet, err := verify.SecondJSONUnlessEquivalent(d.Get("access_policy").(string), aws.ToString(destination.AccessPolicy)) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + policyToSet, err = structure.NormalizeJsonString(policyToSet) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + d.Set("access_policy", policyToSet) d.Set("destination_name", destination.DestinationName) return diags } + +func findDestinationPolicyByName(ctx context.Context, conn *cloudwatchlogs.Client, name string) (*awstypes.Destination, error) { + output, err := findDestinationByName(ctx, conn, name) + + if err != nil { + return nil, err + } + + if output.AccessPolicy == nil { + return nil, tfresource.NewEmptyResultError(name) + } + + return output, err +} diff --git a/internal/service/logs/destination_policy_test.go b/internal/service/logs/destination_policy_test.go index f5949298b89..d8e39bfbeb8 100644 --- a/internal/service/logs/destination_policy_test.go +++ b/internal/service/logs/destination_policy_test.go @@ -63,7 +63,7 @@ func testAccCheckDestinationPolicyExists(ctx context.Context, n string, v *strin conn := acctest.Provider.Meta().(*conns.AWSClient).LogsClient(ctx) - output, err := tflogs.FindDestinationByName(ctx, conn, rs.Primary.ID) + output, err := tflogs.FindDestinationPolicyByName(ctx, conn, rs.Primary.ID) if err != nil { return err diff --git a/internal/service/logs/exports_test.go b/internal/service/logs/exports_test.go index a96b911e119..a86bab8871d 100644 --- a/internal/service/logs/exports_test.go +++ b/internal/service/logs/exports_test.go @@ -6,30 +6,35 @@ package logs // Exports for use in tests only. var ( ResourceAccountPolicy = resourceAccountPolicy + ResourceAnomalyDetector = newAnomalyDetectorResource ResourceDataProtectionPolicy = resourceDataProtectionPolicy ResourceDestination = resourceDestination ResourceDestinationPolicy = resourceDestinationPolicy ResourceGroup = resourceGroup + ResourceIndexPolicy = newIndexPolicyResource ResourceMetricFilter = resourceMetricFilter ResourceQueryDefinition = resourceQueryDefinition ResourceResourcePolicy = resourceResourcePolicy ResourceStream = resourceStream ResourceSubscriptionFilter = resourceSubscriptionFilter - ResourceAnomalyDetector = newResourceAnomalyDetector - FindAccountPolicyByTwoPartKey = findAccountPolicyByTwoPartKey - FindDestinationByName = findDestinationByName - FindLogGroupByName = findLogGroupByName - FindLogStreamByTwoPartKey = findLogStreamByTwoPartKey // nosemgrep:ci.logs-in-var-name - FindMetricFilterByTwoPartKey = findMetricFilterByTwoPartKey - FindQueryDefinitionByTwoPartKey = findQueryDefinitionByTwoPartKey - FindResourcePolicyByName = findResourcePolicyByName - FindSubscriptionFilterByTwoPartKey = findSubscriptionFilterByTwoPartKey - FindLogAnomalyDetectorByARN = findLogAnomalyDetectorByARN + FindAccountPolicyByTwoPartKey = findAccountPolicyByTwoPartKey + FindDataProtectionPolicyByLogGroupName = findDataProtectionPolicyByLogGroupName + FindDestinationByName = findDestinationByName + FindDestinationPolicyByName = findDestinationPolicyByName + FindIndexPolicyByLogGroupName = findIndexPolicyByLogGroupName + FindLogAnomalyDetectorByARN = findLogAnomalyDetectorByARN + FindLogGroupByName = findLogGroupByName + FindLogStreamByTwoPartKey = findLogStreamByTwoPartKey // nosemgrep:ci.logs-in-var-name + FindMetricFilterByTwoPartKey = findMetricFilterByTwoPartKey + FindQueryDefinitionByTwoPartKey = findQueryDefinitionByTwoPartKey + FindResourcePolicyByName = findResourcePolicyByName + FindSubscriptionFilterByTwoPartKey = findSubscriptionFilterByTwoPartKey + TrimLogGroupARNWildcardSuffix = trimLogGroupARNWildcardSuffix ValidLogGroupName = validLogGroupName ValidLogGroupNamePrefix = validLogGroupNamePrefix ValidLogMetricFilterName = validLogMetricFilterName ValidLogMetricFilterTransformationName = validLogMetricFilterTransformationName - ValidStreamName = validStreamName + ValidLogStreamName = validLogStreamName // nosemgrep:ci.logs-in-var-name ) diff --git a/internal/service/logs/generate.go b/internal/service/logs/generate.go index e4084aad6d5..77a88af01db 100644 --- a/internal/service/logs/generate.go +++ b/internal/service/logs/generate.go @@ -1,6 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 +//go:generate go run ../../generate/listpages/main.go -ListOps=DescribeAccountPolicies,DescribeIndexPolicies,DescribeQueryDefinitions,DescribeResourcePolicies //go:generate go run ../../generate/tags/main.go -ListTags -ServiceTagsMap -UpdateTags -CreateTags -KVTValues //go:generate go run ../../generate/servicepackage/main.go //go:generate go run ../../generate/tagstests/main.go diff --git a/internal/service/logs/group.go b/internal/service/logs/group.go index 698068bb374..ec12e0116cd 100644 --- a/internal/service/logs/group.go +++ b/internal/service/logs/group.go @@ -10,7 +10,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/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" @@ -19,6 +19,7 @@ import ( "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" 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" @@ -55,7 +56,7 @@ func resourceGroup() *schema.Resource { Optional: true, Computed: true, ForceNew: true, - ValidateDiagFunc: enum.Validate[types.LogGroupClass](), + ValidateDiagFunc: enum.Validate[awstypes.LogGroupClass](), }, names.AttrName: { Type: schema.TypeString, @@ -94,12 +95,11 @@ func resourceGroup() *schema.Resource { func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) name := create.Name(d.Get(names.AttrName).(string), d.Get(names.AttrNamePrefix).(string)) input := &cloudwatchlogs.CreateLogGroupInput{ - LogGroupClass: types.LogGroupClass(d.Get("log_group_class").(string)), + LogGroupClass: awstypes.LogGroupClass(d.Get("log_group_class").(string)), LogGroupName: aws.String(name), Tags: getTagsIn(ctx), } @@ -136,7 +136,6 @@ func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta inter func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) lg, err := findLogGroupByName(ctx, conn, d.Id()) @@ -151,7 +150,7 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interfa return sdkdiag.AppendErrorf(diags, "reading CloudWatch Logs Log Group (%s): %s", d.Id(), err) } - d.Set(names.AttrARN, TrimLogGroupARNWildcardSuffix(aws.ToString(lg.Arn))) + d.Set(names.AttrARN, trimLogGroupARNWildcardSuffix(aws.ToString(lg.Arn))) d.Set(names.AttrKMSKeyID, lg.KmsKeyId) d.Set("log_group_class", lg.LogGroupClass) d.Set(names.AttrName, lg.LogGroupName) @@ -165,7 +164,6 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interfa func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) if d.HasChange("retention_in_days") { @@ -183,9 +181,11 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta inter return sdkdiag.AppendErrorf(diags, "setting CloudWatch Logs Log Group (%s) retention policy: %s", d.Id(), err) } } else { - _, err := conn.DeleteRetentionPolicy(ctx, &cloudwatchlogs.DeleteRetentionPolicyInput{ + input := &cloudwatchlogs.DeleteRetentionPolicyInput{ LogGroupName: aws.String(d.Id()), - }) + } + + _, err := conn.DeleteRetentionPolicy(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "deleting CloudWatch Logs Log Group (%s) retention policy: %s", d.Id(), err) @@ -195,18 +195,22 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta inter if d.HasChange(names.AttrKMSKeyID) { if v, ok := d.GetOk(names.AttrKMSKeyID); ok { - _, err := conn.AssociateKmsKey(ctx, &cloudwatchlogs.AssociateKmsKeyInput{ + input := &cloudwatchlogs.AssociateKmsKeyInput{ KmsKeyId: aws.String(v.(string)), LogGroupName: aws.String(d.Id()), - }) + } + + _, err := conn.AssociateKmsKey(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "associating CloudWatch Logs Log Group (%s) KMS key: %s", d.Id(), err) } } else { - _, err := conn.DisassociateKmsKey(ctx, &cloudwatchlogs.DisassociateKmsKeyInput{ + input := &cloudwatchlogs.DisassociateKmsKeyInput{ LogGroupName: aws.String(d.Id()), - }) + } + + _, err := conn.DisassociateKmsKey(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "disassociating CloudWatch Logs Log Group (%s) KMS key: %s", d.Id(), err) @@ -219,22 +223,21 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta inter func resourceGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).LogsClient(ctx) if v, ok := d.GetOk(names.AttrSkipDestroy); ok && v.(bool) { log.Printf("[DEBUG] Retaining CloudWatch Logs Log Group: %s", d.Id()) return diags } - conn := meta.(*conns.AWSClient).LogsClient(ctx) - log.Printf("[INFO] Deleting CloudWatch Logs Log Group: %s", d.Id()) - _, err := tfresource.RetryWhenIsAErrorMessageContains[*types.OperationAbortedException](ctx, 1*time.Minute, func() (interface{}, error) { + _, err := tfresource.RetryWhenIsAErrorMessageContains[*awstypes.OperationAbortedException](ctx, 1*time.Minute, func() (interface{}, error) { return conn.DeleteLogGroup(ctx, &cloudwatchlogs.DeleteLogGroupInput{ LogGroupName: aws.String(d.Id()), }) }, "try again") - if errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return diags } @@ -245,11 +248,29 @@ func resourceGroupDelete(ctx context.Context, d *schema.ResourceData, meta inter return diags } -func findLogGroupByName(ctx context.Context, conn *cloudwatchlogs.Client, name string) (*types.LogGroup, error) { - input := &cloudwatchlogs.DescribeLogGroupsInput{ +func findLogGroupByName(ctx context.Context, conn *cloudwatchlogs.Client, name string) (*awstypes.LogGroup, error) { + input := cloudwatchlogs.DescribeLogGroupsInput{ LogGroupNamePrefix: aws.String(name), } + return findLogGroup(ctx, conn, &input, func(v *awstypes.LogGroup) bool { + return aws.ToString(v.LogGroupName) == name + }) +} + +func findLogGroup(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeLogGroupsInput, filter tfslices.Predicate[*awstypes.LogGroup]) (*awstypes.LogGroup, error) { + output, err := findLogGroups(ctx, conn, input, filter) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findLogGroups(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeLogGroupsInput, filter tfslices.Predicate[*awstypes.LogGroup]) ([]awstypes.LogGroup, error) { + var output []awstypes.LogGroup + pages := cloudwatchlogs.NewDescribeLogGroupsPaginator(conn, input) for pages.HasMorePages() { page, err := pages.NextPage(ctx) @@ -259,11 +280,11 @@ func findLogGroupByName(ctx context.Context, conn *cloudwatchlogs.Client, name s } for _, v := range page.LogGroups { - if aws.ToString(v.LogGroupName) == name { - return &v, nil + if filter(&v) { + output = append(output, v) } } } - return nil, tfresource.NewEmptyResultError(input) + return output, nil } diff --git a/internal/service/logs/group_data_source.go b/internal/service/logs/group_data_source.go index bf757b2af28..651c3f210c4 100644 --- a/internal/service/logs/group_data_source.go +++ b/internal/service/logs/group_data_source.go @@ -53,7 +53,6 @@ func dataSourceGroup() *schema.Resource { func dataSourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) name := d.Get(names.AttrName).(string) @@ -64,7 +63,7 @@ func dataSourceGroupRead(ctx context.Context, d *schema.ResourceData, meta inter } d.SetId(name) - d.Set(names.AttrARN, TrimLogGroupARNWildcardSuffix(aws.ToString(logGroup.Arn))) + d.Set(names.AttrARN, trimLogGroupARNWildcardSuffix(aws.ToString(logGroup.Arn))) d.Set(names.AttrCreationTime, logGroup.CreationTime) d.Set(names.AttrKMSKeyID, logGroup.KmsKeyId) d.Set("log_group_class", logGroup.LogGroupClass) diff --git a/internal/service/logs/groups_data_source.go b/internal/service/logs/groups_data_source.go index 93863336ce4..77bdb8c06be 100644 --- a/internal/service/logs/groups_data_source.go +++ b/internal/service/logs/groups_data_source.go @@ -8,15 +8,16 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/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_cloudwatch_log_groups") +// @SDKDataSource("aws_cloudwatch_log_groups", name="Log Groups") func dataSourceGroups() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceGroupsRead, @@ -42,37 +43,25 @@ func dataSourceGroups() *schema.Resource { func dataSourceGroupsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) - input := &cloudwatchlogs.DescribeLogGroupsInput{} - + input := cloudwatchlogs.DescribeLogGroupsInput{} if v, ok := d.GetOk("log_group_name_prefix"); ok { input.LogGroupNamePrefix = aws.String(v.(string)) } - var output []types.LogGroup - - pages := cloudwatchlogs.NewDescribeLogGroupsPaginator(conn, input) - for pages.HasMorePages() { - page, err := pages.NextPage(ctx) - - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading CloudWatch Log Groups: %s", err) - } + output, err := findLogGroups(ctx, conn, &input, tfslices.PredicateTrue[*awstypes.LogGroup]()) - output = append(output, page.LogGroups...) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading CloudWatch Log Groups: %s", err) } d.SetId(meta.(*conns.AWSClient).Region(ctx)) - var arns, logGroupNames []string - - for _, r := range output { - arns = append(arns, TrimLogGroupARNWildcardSuffix(aws.ToString(r.Arn))) - logGroupNames = append(logGroupNames, aws.ToString(r.LogGroupName)) + for _, v := range output { + arns = append(arns, trimLogGroupARNWildcardSuffix(aws.ToString(v.Arn))) + logGroupNames = append(logGroupNames, aws.ToString(v.LogGroupName)) } - d.Set(names.AttrARNs, arns) d.Set("log_group_names", logGroupNames) diff --git a/internal/service/logs/index_policy.go b/internal/service/logs/index_policy.go new file mode 100644 index 00000000000..4f5e7c908ec --- /dev/null +++ b/internal/service/logs/index_policy.go @@ -0,0 +1,238 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package logs + +import ( + "context" + "fmt" + "strings" + + "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/cloudwatchlogs" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_cloudwatch_log_index_policy", name="Index Policy") +func newIndexPolicyResource(context.Context) (resource.ResourceWithConfigure, error) { + r := &indexPolicyResource{} + return r, nil +} + +type indexPolicyResource struct { + framework.ResourceWithConfigure +} + +func (*indexPolicyResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_cloudwatch_log_index_policy" +} + +func (r *indexPolicyResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrLogGroupName: schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "policy_document": schema.StringAttribute{ + CustomType: jsontypes.NormalizedType{}, + Required: true, + Description: "Field index filter policy, in JSON", + }, + }, + } +} + +func (r *indexPolicyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data indexPolicyResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().LogsClient(ctx) + + input := cloudwatchlogs.PutIndexPolicyInput{ + LogGroupIdentifier: fwflex.StringFromFramework(ctx, data.LogGroupName), + PolicyDocument: fwflex.StringFromFramework(ctx, data.PolicyDocument), + } + + _, err := conn.PutIndexPolicy(ctx, &input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("creating CloudWatch Logs Index Policy (%s)", data.LogGroupName.ValueString()), err.Error()) + + return + } + + response.Diagnostics.Append(response.State.Set(ctx, data)...) +} + +func (r *indexPolicyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data indexPolicyResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().LogsClient(ctx) + + output, err := findIndexPolicyByLogGroupName(ctx, conn, data.LogGroupName.ValueString()) + + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("reading CloudWatch Logs Index Policy (%s)", data.LogGroupName.ValueString()), err.Error()) + + return + } + + // Set attributes for import. + data.LogGroupName = fwflex.StringValueToFramework(ctx, logGroupIdentifierToName(aws.ToString(output.LogGroupIdentifier))) + data.PolicyDocument = jsontypes.NewNormalizedValue(aws.ToString(output.PolicyDocument)) + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *indexPolicyResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var new indexPolicyResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().LogsClient(ctx) + + input := cloudwatchlogs.PutIndexPolicyInput{ + LogGroupIdentifier: fwflex.StringFromFramework(ctx, new.LogGroupName), + PolicyDocument: fwflex.StringFromFramework(ctx, new.PolicyDocument), + } + + _, err := conn.PutIndexPolicy(ctx, &input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("updating CloudWatch Logs Index Policy (%s)", new.LogGroupName.ValueString()), err.Error()) + + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &new)...) +} + +func (r *indexPolicyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data indexPolicyResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().LogsClient(ctx) + + _, err := conn.DeleteIndexPolicy(ctx, &cloudwatchlogs.DeleteIndexPolicyInput{ + LogGroupIdentifier: fwflex.StringFromFramework(ctx, data.LogGroupName), + }) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting CloudWatch Logs Index Policy (%s)", data.LogGroupName.ValueString()), err.Error()) + + return + } +} + +func (r *indexPolicyResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root(names.AttrLogGroupName), request, response) +} + +func findIndexPolicyByLogGroupName(ctx context.Context, conn *cloudwatchlogs.Client, name string) (*awstypes.IndexPolicy, error) { + input := cloudwatchlogs.DescribeIndexPoliciesInput{ + LogGroupIdentifiers: []string{name}, + } + output, err := findIndexPolicy(ctx, conn, &input) + + if err != nil { + return nil, err + } + + if output.PolicyDocument == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, err +} + +func findIndexPolicy(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeIndexPoliciesInput) (*awstypes.IndexPolicy, error) { + output, err := findIndexPolicies(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findIndexPolicies(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeIndexPoliciesInput) ([]awstypes.IndexPolicy, error) { + var output []awstypes.IndexPolicy + + err := describeIndexPoliciesPages(ctx, conn, input, func(page *cloudwatchlogs.DescribeIndexPoliciesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + output = append(output, page.IndexPolicies...) + + return !lastPage + }) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +type indexPolicyResourceModel struct { + LogGroupName types.String `tfsdk:"log_group_name"` + PolicyDocument jsontypes.Normalized `tfsdk:"policy_document"` +} + +func logGroupIdentifierToName(identifier string) string { + arn, err := arn.Parse(identifier) + if err != nil { + return identifier + } + + return strings.TrimPrefix(arn.Resource, "log-group:") +} diff --git a/internal/service/logs/index_policy_test.go b/internal/service/logs/index_policy_test.go new file mode 100644 index 00000000000..bb2cc3cfa71 --- /dev/null +++ b/internal/service/logs/index_policy_test.go @@ -0,0 +1,177 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package logs_test + +import ( + "context" + "fmt" + "testing" + + 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" + tflogs "github.com/hashicorp/terraform-provider-aws/internal/service/logs" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccLogsIndexPolicy_basic(t *testing.T) { + ctx := acctest.Context(t) + logGroupName := "/aws/testacc/index-policy-" + sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + policyDocument := `{"Fields":["eventName"]}` + resourceName := "aws_cloudwatch_log_index_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.CloudWatchEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIndexPolicyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccIndexPolicyConfig_basic(logGroupName, policyDocument), + Check: resource.ComposeTestCheckFunc( + testAccCheckIndexPolicyExists(ctx, resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccIndexPolicyImportStateIDFunc(resourceName), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: names.AttrLogGroupName, + }, + }, + }) +} + +func TestAccLogsIndexPolicy_disappears(t *testing.T) { + ctx := acctest.Context(t) + logGroupName := "/aws/testacc/index-policy-" + sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + policyDocument := `{"Fields":["eventName"]}` + resourceName := "aws_cloudwatch_log_index_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.CloudWatchEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIndexPolicyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccIndexPolicyConfig_basic(logGroupName, policyDocument), + Check: resource.ComposeTestCheckFunc( + testAccCheckIndexPolicyExists(ctx, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tflogs.ResourceIndexPolicy, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccLogsIndexPolicy_update(t *testing.T) { + ctx := acctest.Context(t) + logGroupName := "/aws/testacc/index-policy-" + sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + policyDocument1 := `{"Fields":["eventName"]}` + policyDocument2 := `{"Fields": ["eventName", "requestId"]}` + resourceName := "aws_cloudwatch_log_index_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.CloudWatchEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIndexPolicyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccIndexPolicyConfig_basic(logGroupName, policyDocument1), + Check: resource.ComposeTestCheckFunc( + testAccCheckIndexPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "policy_document", policyDocument1), + ), + }, + { + Config: testAccIndexPolicyConfig_basic(logGroupName, policyDocument2), + Check: resource.ComposeTestCheckFunc( + testAccCheckIndexPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "policy_document", policyDocument2), + ), + }, + }, + }) +} + +func testAccCheckIndexPolicyDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).LogsClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloudwatch_log_index_policy" { + continue + } + + _, err := tflogs.FindIndexPolicyByLogGroupName(ctx, conn, rs.Primary.Attributes[names.AttrLogGroupName]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("CloudWatch Logs Index Policy still exists: %s", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckIndexPolicyExists(ctx context.Context, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).LogsClient(ctx) + + _, err := tflogs.FindIndexPolicyByLogGroupName(ctx, conn, rs.Primary.Attributes[names.AttrLogGroupName]) + + return err + } +} + +func testAccIndexPolicyImportStateIDFunc(n string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[n] + if !ok { + return "", fmt.Errorf("Not found: %s", n) + } + + return rs.Primary.Attributes[names.AttrLogGroupName], nil + } +} + +func testAccIndexPolicyConfig_basic(logGroupName string, policyDocument string) string { + return fmt.Sprintf(` +resource "aws_cloudwatch_log_group" "test" { + name = %[1]q +} + +resource "aws_cloudwatch_log_index_policy" "test" { + log_group_name = aws_cloudwatch_log_group.test.name + policy_document = %[2]q +} +`, logGroupName, policyDocument) +} diff --git a/internal/service/logs/list_pages.go b/internal/service/logs/list_pages.go deleted file mode 100644 index a0dd2ac4b36..00000000000 --- a/internal/service/logs/list_pages.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// Code generated by internal/generate/listpages/main.go and migrated to AWS SDK for Go v2. - -package logs - -import ( - "context" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" -) - -func describeQueryDefinitionsPages(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeQueryDefinitionsInput, fn func(*cloudwatchlogs.DescribeQueryDefinitionsOutput, bool) bool) error { - for { - output, err := conn.DescribeQueryDefinitions(ctx, input) - if err != nil { - return err - } - - lastPage := aws.ToString(output.NextToken) == "" - if !fn(output, lastPage) || lastPage { - break - } - - input.NextToken = output.NextToken - } - return nil -} -func describeResourcePoliciesPages(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeResourcePoliciesInput, fn func(*cloudwatchlogs.DescribeResourcePoliciesOutput, bool) bool) error { - for { - output, err := conn.DescribeResourcePolicies(ctx, input) - if err != nil { - return err - } - - lastPage := aws.ToString(output.NextToken) == "" - if !fn(output, lastPage) || lastPage { - break - } - - input.NextToken = output.NextToken - } - return nil -} diff --git a/internal/service/logs/list_pages_gen.go b/internal/service/logs/list_pages_gen.go new file mode 100644 index 00000000000..e9e409f7bff --- /dev/null +++ b/internal/service/logs/list_pages_gen.go @@ -0,0 +1,75 @@ +// Code generated by "internal/generate/listpages/main.go -ListOps=DescribeAccountPolicies,DescribeIndexPolicies,DescribeQueryDefinitions,DescribeResourcePolicies"; DO NOT EDIT. + +package logs + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" +) + +func describeAccountPoliciesPages(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeAccountPoliciesInput, fn func(*cloudwatchlogs.DescribeAccountPoliciesOutput, bool) bool) error { + for { + output, err := conn.DescribeAccountPolicies(ctx, input) + if err != nil { + return err + } + + lastPage := aws.ToString(output.NextToken) == "" + if !fn(output, lastPage) || lastPage { + break + } + + input.NextToken = output.NextToken + } + return nil +} +func describeIndexPoliciesPages(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeIndexPoliciesInput, fn func(*cloudwatchlogs.DescribeIndexPoliciesOutput, bool) bool) error { + for { + output, err := conn.DescribeIndexPolicies(ctx, input) + if err != nil { + return err + } + + lastPage := aws.ToString(output.NextToken) == "" + if !fn(output, lastPage) || lastPage { + break + } + + input.NextToken = output.NextToken + } + return nil +} +func describeQueryDefinitionsPages(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeQueryDefinitionsInput, fn func(*cloudwatchlogs.DescribeQueryDefinitionsOutput, bool) bool) error { + for { + output, err := conn.DescribeQueryDefinitions(ctx, input) + if err != nil { + return err + } + + lastPage := aws.ToString(output.NextToken) == "" + if !fn(output, lastPage) || lastPage { + break + } + + input.NextToken = output.NextToken + } + return nil +} +func describeResourcePoliciesPages(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeResourcePoliciesInput, fn func(*cloudwatchlogs.DescribeResourcePoliciesOutput, bool) bool) error { + for { + output, err := conn.DescribeResourcePolicies(ctx, input) + if err != nil { + return err + } + + lastPage := aws.ToString(output.NextToken) == "" + if !fn(output, lastPage) || lastPage { + break + } + + input.NextToken = output.NextToken + } + return nil +} diff --git a/internal/service/logs/metric_filter.go b/internal/service/logs/metric_filter.go index bb97e056a73..400a85827a9 100644 --- a/internal/service/logs/metric_filter.go +++ b/internal/service/logs/metric_filter.go @@ -12,7 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/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" @@ -23,11 +23,12 @@ import ( "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/types/nullable" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_cloudwatch_log_metric_filter") +// @SDKResource("aws_cloudwatch_log_metric_filter", name="Metric Filter") func resourceMetricFilter() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceMetricFilterPut, @@ -75,8 +76,8 @@ func resourceMetricFilter() *schema.Resource { names.AttrUnit: { Type: schema.TypeString, Optional: true, - Default: types.StandardUnitNone, - ValidateDiagFunc: enum.Validate[types.StandardUnit](), + Default: awstypes.StandardUnitNone, + ValidateDiagFunc: enum.Validate[awstypes.StandardUnit](), }, names.AttrValue: { Type: schema.TypeString, @@ -110,7 +111,6 @@ func resourceMetricFilter() *schema.Resource { func resourceMetricFilterPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) name := d.Get(names.AttrName).(string) @@ -144,7 +144,6 @@ func resourceMetricFilterPut(ctx context.Context, d *schema.ResourceData, meta i func resourceMetricFilterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) mf, err := findMetricFilterByTwoPartKey(ctx, conn, d.Get(names.AttrLogGroupName).(string), d.Id()) @@ -171,7 +170,6 @@ func resourceMetricFilterRead(ctx context.Context, d *schema.ResourceData, meta func resourceMetricFilterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) // Creating multiple filters on the same log group can sometimes cause @@ -187,7 +185,7 @@ func resourceMetricFilterDelete(ctx context.Context, d *schema.ResourceData, met LogGroupName: aws.String(d.Get(names.AttrLogGroupName).(string)), }) - if errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return diags } @@ -211,17 +209,35 @@ func resourceMetricFilterImport(d *schema.ResourceData, meta interface{}) ([]*sc return []*schema.ResourceData{d}, nil } -func findMetricFilterByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, logGroupName, name string) (*types.MetricFilter, error) { - input := &cloudwatchlogs.DescribeMetricFiltersInput{ +func findMetricFilterByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, logGroupName, name string) (*awstypes.MetricFilter, error) { + input := cloudwatchlogs.DescribeMetricFiltersInput{ FilterNamePrefix: aws.String(name), LogGroupName: aws.String(logGroupName), } + return findMetricFilter(ctx, conn, &input, func(v *awstypes.MetricFilter) bool { + return aws.ToString(v.FilterName) == name + }) +} + +func findMetricFilter(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeMetricFiltersInput, filter tfslices.Predicate[*awstypes.MetricFilter]) (*awstypes.MetricFilter, error) { + output, err := findMetricFilters(ctx, conn, input, filter) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findMetricFilters(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeMetricFiltersInput, filter tfslices.Predicate[*awstypes.MetricFilter]) ([]awstypes.MetricFilter, error) { + var output []awstypes.MetricFilter + pages := cloudwatchlogs.NewDescribeMetricFiltersPaginator(conn, input) for pages.HasMorePages() { page, err := pages.NextPage(ctx) - if errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, @@ -233,21 +249,21 @@ func findMetricFilterByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Clie } for _, v := range page.MetricFilters { - if aws.ToString(v.FilterName) == name { - return &v, nil + if filter(&v) { + output = append(output, v) } } } - return nil, tfresource.NewEmptyResultError(input) + return output, nil } -func expandMetricTransformation(tfMap map[string]interface{}) *types.MetricTransformation { +func expandMetricTransformation(tfMap map[string]interface{}) *awstypes.MetricTransformation { if tfMap == nil { return nil } - apiObject := &types.MetricTransformation{} + apiObject := &awstypes.MetricTransformation{} if v, ok := tfMap[names.AttrDefaultValue].(string); ok { if v, null, _ := nullable.Float(v).ValueFloat64(); !null { @@ -268,7 +284,7 @@ func expandMetricTransformation(tfMap map[string]interface{}) *types.MetricTrans } if v, ok := tfMap[names.AttrUnit].(string); ok && v != "" { - apiObject.Unit = types.StandardUnit(v) + apiObject.Unit = awstypes.StandardUnit(v) } if v, ok := tfMap[names.AttrValue].(string); ok && v != "" { @@ -278,12 +294,12 @@ func expandMetricTransformation(tfMap map[string]interface{}) *types.MetricTrans return apiObject } -func expandMetricTransformations(tfList []interface{}) []types.MetricTransformation { +func expandMetricTransformations(tfList []interface{}) []awstypes.MetricTransformation { if len(tfList) == 0 { return nil } - var apiObjects []types.MetricTransformation + var apiObjects []awstypes.MetricTransformation for _, tfMapRaw := range tfList { tfMap, ok := tfMapRaw.(map[string]interface{}) @@ -304,7 +320,7 @@ func expandMetricTransformations(tfList []interface{}) []types.MetricTransformat return apiObjects } -func flattenMetricTransformation(apiObject types.MetricTransformation) map[string]interface{} { +func flattenMetricTransformation(apiObject awstypes.MetricTransformation) map[string]interface{} { tfMap := map[string]interface{}{ names.AttrUnit: apiObject.Unit, } @@ -332,7 +348,7 @@ func flattenMetricTransformation(apiObject types.MetricTransformation) map[strin return tfMap } -func flattenMetricTransformations(apiObjects []types.MetricTransformation) []interface{} { +func flattenMetricTransformations(apiObjects []awstypes.MetricTransformation) []interface{} { if len(apiObjects) == 0 { return nil } diff --git a/internal/service/logs/query_definition.go b/internal/service/logs/query_definition.go index d3fde0ec435..3967f5f8e68 100644 --- a/internal/service/logs/query_definition.go +++ b/internal/service/logs/query_definition.go @@ -12,7 +12,7 @@ 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/cloudwatchlogs" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/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" @@ -20,12 +20,13 @@ import ( "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" "github.com/hashicorp/terraform-provider-aws/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_cloudwatch_query_definition") +// @SDKResource("aws_cloudwatch_query_definition", name="Query Definition") func resourceQueryDefinition() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceQueryDefinitionPut, @@ -68,7 +69,6 @@ func resourceQueryDefinition() *schema.Resource { func resourceQueryDefinitionPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) name := d.Get(names.AttrName).(string) @@ -100,7 +100,6 @@ func resourceQueryDefinitionPut(ctx context.Context, d *schema.ResourceData, met func resourceQueryDefinitionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) result, err := findQueryDefinitionByTwoPartKey(ctx, conn, d.Get(names.AttrName).(string), d.Id()) @@ -125,7 +124,6 @@ func resourceQueryDefinitionRead(ctx context.Context, d *schema.ResourceData, me func resourceQueryDefinitionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) log.Printf("[INFO] Deleting CloudWatch Logs Query Definition: %s", d.Id()) @@ -133,7 +131,7 @@ func resourceQueryDefinitionDelete(ctx context.Context, d *schema.ResourceData, QueryDefinitionId: aws.String(d.Id()), }) - if errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return diags } @@ -165,12 +163,29 @@ func resourceQueryDefinitionImport(ctx context.Context, d *schema.ResourceData, return []*schema.ResourceData{d}, nil } -func findQueryDefinitionByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, name, queryDefinitionID string) (*types.QueryDefinition, error) { - input := &cloudwatchlogs.DescribeQueryDefinitionsInput{} +func findQueryDefinitionByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, name, queryDefinitionID string) (*awstypes.QueryDefinition, error) { + input := cloudwatchlogs.DescribeQueryDefinitionsInput{} if name != "" { input.QueryDefinitionNamePrefix = aws.String(name) } - var output *types.QueryDefinition + + return findQueryDefinition(ctx, conn, &input, func(v *awstypes.QueryDefinition) bool { + return aws.ToString(v.QueryDefinitionId) == queryDefinitionID + }) +} + +func findQueryDefinition(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeQueryDefinitionsInput, filter tfslices.Predicate[*awstypes.QueryDefinition]) (*awstypes.QueryDefinition, error) { + output, err := findQueryDefinitions(ctx, conn, input, filter) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findQueryDefinitions(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeQueryDefinitionsInput, filter tfslices.Predicate[*awstypes.QueryDefinition]) ([]awstypes.QueryDefinition, error) { + var output []awstypes.QueryDefinition err := describeQueryDefinitionsPages(ctx, conn, input, func(page *cloudwatchlogs.DescribeQueryDefinitionsOutput, lastPage bool) bool { if page == nil { @@ -178,10 +193,8 @@ func findQueryDefinitionByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.C } for _, v := range page.QueryDefinitions { - if aws.ToString(v.QueryDefinitionId) == queryDefinitionID { - output = &v - - return false + if filter(&v) { + output = append(output, v) } } @@ -192,9 +205,5 @@ func findQueryDefinitionByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.C return nil, err } - if output == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return output, err + return output, nil } diff --git a/internal/service/logs/resource_policy.go b/internal/service/logs/resource_policy.go index 6d73c4f7ec6..6b66676b794 100644 --- a/internal/service/logs/resource_policy.go +++ b/internal/service/logs/resource_policy.go @@ -9,18 +9,20 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/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/structure" "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/sdkv2" + 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" ) -// @SDKResource("aws_cloudwatch_log_resource_policy") +// @SDKResource("aws_cloudwatch_log_resource_policy", name="Resource Policy") func resourceResourcePolicy() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceResourcePolicyPut, @@ -36,17 +38,7 @@ func resourceResourcePolicy() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "policy_document": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validResourcePolicyDocument, - DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, - DiffSuppressOnRefresh: true, - StateFunc: func(v interface{}) string { - json, _ := structure.NormalizeJsonString(v) - return json - }, - }, + "policy_document": sdkv2.IAMPolicyDocumentSchemaRequired(), "policy_name": { Type: schema.TypeString, Required: true, @@ -58,13 +50,11 @@ func resourceResourcePolicy() *schema.Resource { func resourceResourcePolicyPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) policy, err := structure.NormalizeJsonString(d.Get("policy_document").(string)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "policy (%s) is invalid JSON: %s", policy, err) + return sdkdiag.AppendFromErr(diags, err) } name := d.Get("policy_name").(string) @@ -73,20 +63,21 @@ func resourceResourcePolicyPut(ctx context.Context, d *schema.ResourceData, meta PolicyName: aws.String(name), } - output, err := conn.PutResourcePolicy(ctx, input) + _, err = conn.PutResourcePolicy(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "creating CloudWatch Logs Resource Policy (%s): %s", name, err) } - d.SetId(aws.ToString(output.ResourcePolicy.PolicyName)) + if d.IsNewResource() { + d.SetId(name) + } return append(diags, resourceResourcePolicyRead(ctx, d, meta)...) } func resourceResourcePolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) resourcePolicy, err := findResourcePolicyByName(ctx, conn, d.Id()) @@ -102,15 +93,13 @@ func resourceResourcePolicyRead(ctx context.Context, d *schema.ResourceData, met } policyToSet, err := verify.SecondJSONUnlessEquivalent(d.Get("policy_document").(string), aws.ToString(resourcePolicy.PolicyDocument)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "while setting policy (%s), encountered: %s", policyToSet, err) + return sdkdiag.AppendFromErr(diags, err) } policyToSet, err = structure.NormalizeJsonString(policyToSet) - if err != nil { - return sdkdiag.AppendErrorf(diags, "policy (%s) is invalid JSON: %s", policyToSet, err) + return sdkdiag.AppendFromErr(diags, err) } d.Set("policy_document", policyToSet) @@ -120,7 +109,6 @@ func resourceResourcePolicyRead(ctx context.Context, d *schema.ResourceData, met func resourceResourcePolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) log.Printf("[DEBUG] Deleting CloudWatch Logs Resource Policy: %s", d.Id()) @@ -128,7 +116,7 @@ func resourceResourcePolicyDelete(ctx context.Context, d *schema.ResourceData, m PolicyName: aws.String(d.Id()), }) - if errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return diags } @@ -139,9 +127,35 @@ func resourceResourcePolicyDelete(ctx context.Context, d *schema.ResourceData, m return diags } -func findResourcePolicyByName(ctx context.Context, conn *cloudwatchlogs.Client, name string) (*types.ResourcePolicy, error) { - input := &cloudwatchlogs.DescribeResourcePoliciesInput{} - var output *types.ResourcePolicy +func findResourcePolicyByName(ctx context.Context, conn *cloudwatchlogs.Client, name string) (*awstypes.ResourcePolicy, error) { + input := cloudwatchlogs.DescribeResourcePoliciesInput{} + output, err := findResourcePolicy(ctx, conn, &input, func(v *awstypes.ResourcePolicy) bool { + return aws.ToString(v.PolicyName) == name + }) + + if err != nil { + return nil, err + } + + if output.PolicyDocument == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, err +} + +func findResourcePolicy(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeResourcePoliciesInput, filter tfslices.Predicate[*awstypes.ResourcePolicy]) (*awstypes.ResourcePolicy, error) { + output, err := findResourcePolicies(ctx, conn, input, filter) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findResourcePolicies(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeResourcePoliciesInput, filter tfslices.Predicate[*awstypes.ResourcePolicy]) ([]awstypes.ResourcePolicy, error) { + var output []awstypes.ResourcePolicy err := describeResourcePoliciesPages(ctx, conn, input, func(page *cloudwatchlogs.DescribeResourcePoliciesOutput, lastPage bool) bool { if page == nil { @@ -149,10 +163,8 @@ func findResourcePolicyByName(ctx context.Context, conn *cloudwatchlogs.Client, } for _, v := range page.ResourcePolicies { - if aws.ToString(v.PolicyName) == name { - output = &v - - return false + if filter(&v) { + output = append(output, v) } } @@ -163,9 +175,5 @@ func findResourcePolicyByName(ctx context.Context, conn *cloudwatchlogs.Client, return nil, err } - if output == nil { - return nil, tfresource.NewEmptyResultError(input) - } - return output, nil } diff --git a/internal/service/logs/service_package_gen.go b/internal/service/logs/service_package_gen.go index 8a4105c1e72..4d0e642de5b 100644 --- a/internal/service/logs/service_package_gen.go +++ b/internal/service/logs/service_package_gen.go @@ -21,12 +21,16 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ { - Factory: newResourceAnomalyDetector, + Factory: newAnomalyDetectorResource, Name: "Anomaly Detector", Tags: &types.ServicePackageResourceTags{ IdentifierAttribute: names.AttrARN, }, }, + { + Factory: newIndexPolicyResource, + Name: "Index Policy", + }, } } @@ -47,6 +51,7 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac { Factory: dataSourceGroups, TypeName: "aws_cloudwatch_log_groups", + Name: "Log Groups", }, } } @@ -61,6 +66,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka { Factory: resourceDataProtectionPolicy, TypeName: "aws_cloudwatch_log_data_protection_policy", + Name: "Data Protection Policy", }, { Factory: resourceDestination, @@ -73,6 +79,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka { Factory: resourceDestinationPolicy, TypeName: "aws_cloudwatch_log_destination_policy", + Name: "Destination Policy", }, { Factory: resourceGroup, @@ -85,22 +92,27 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka { Factory: resourceMetricFilter, TypeName: "aws_cloudwatch_log_metric_filter", + Name: "Metric Filter", }, { Factory: resourceResourcePolicy, TypeName: "aws_cloudwatch_log_resource_policy", + Name: "Resource Policy", }, { Factory: resourceStream, TypeName: "aws_cloudwatch_log_stream", + Name: "Log Stream", }, { Factory: resourceSubscriptionFilter, TypeName: "aws_cloudwatch_log_subscription_filter", + Name: "Subscription Filter", }, { Factory: resourceQueryDefinition, TypeName: "aws_cloudwatch_query_definition", + Name: "Query Definition", }, } } diff --git a/internal/service/logs/stream.go b/internal/service/logs/stream.go index dfa90ffceea..abdf0705e5e 100644 --- a/internal/service/logs/stream.go +++ b/internal/service/logs/stream.go @@ -9,21 +9,21 @@ import ( "log" "strings" - "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/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/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" ) -// @SDKResource("aws_cloudwatch_log_stream") +// @SDKResource("aws_cloudwatch_log_stream", name="Log Stream") func resourceStream() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceStreamCreate, @@ -48,7 +48,7 @@ func resourceStream() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validStreamName, + ValidateFunc: validLogStreamName, }, }, } @@ -56,7 +56,6 @@ func resourceStream() *schema.Resource { func resourceStreamCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) name := d.Get(names.AttrName).(string) @@ -86,7 +85,6 @@ func resourceStreamCreate(ctx context.Context, d *schema.ResourceData, meta inte func resourceStreamRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) ls, err := findLogStreamByTwoPartKey(ctx, conn, d.Get(names.AttrLogGroupName).(string), d.Id()) @@ -109,7 +107,6 @@ func resourceStreamRead(ctx context.Context, d *schema.ResourceData, meta interf func resourceStreamDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) log.Printf("[INFO] Deleting CloudWatch Logs Log Stream: %s", d.Id()) @@ -118,7 +115,7 @@ func resourceStreamDelete(ctx context.Context, d *schema.ResourceData, meta inte LogStreamName: aws.String(d.Id()), }) - if errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return diags } @@ -144,17 +141,35 @@ func resourceStreamImport(d *schema.ResourceData, meta interface{}) ([]*schema.R return []*schema.ResourceData{d}, nil } -func findLogStreamByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, logGroupName, name string) (*types.LogStream, error) { // nosemgrep:ci.logs-in-func-name - input := &cloudwatchlogs.DescribeLogStreamsInput{ +func findLogStreamByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, logGroupName, name string) (*awstypes.LogStream, error) { // nosemgrep:ci.logs-in-func-name + input := cloudwatchlogs.DescribeLogStreamsInput{ LogGroupName: aws.String(logGroupName), LogStreamNamePrefix: aws.String(name), } + return findLogStream(ctx, conn, &input, func(v *awstypes.LogStream) bool { + return aws.ToString(v.LogStreamName) == name + }) +} + +func findLogStream(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeLogStreamsInput, filter tfslices.Predicate[*awstypes.LogStream]) (*awstypes.LogStream, error) { // nosemgrep:ci.logs-in-func-name + output, err := findLogStreams(ctx, conn, input, filter) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findLogStreams(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeLogStreamsInput, filter tfslices.Predicate[*awstypes.LogStream]) ([]awstypes.LogStream, error) { // nosemgrep:ci.logs-in-func-name + var output []awstypes.LogStream + pages := cloudwatchlogs.NewDescribeLogStreamsPaginator(conn, input) for pages.HasMorePages() { page, err := pages.NextPage(ctx) - if errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, @@ -166,25 +181,11 @@ func findLogStreamByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, } for _, v := range page.LogStreams { - if aws.ToString(v.LogStreamName) == name { - return &v, nil + if filter(&v) { + output = append(output, v) } } } - return nil, tfresource.NewEmptyResultError(input) -} - -func validStreamName(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - if regexache.MustCompile(`:`).MatchString(value) { - errors = append(errors, fmt.Errorf( - "colons not allowed in %q:", k)) - } - if len(value) < 1 || len(value) > 512 { - errors = append(errors, fmt.Errorf( - "%q must be between 1 and 512 characters: %q", k, value)) - } - - return + return output, nil } diff --git a/internal/service/logs/subscription_filter.go b/internal/service/logs/subscription_filter.go index 39ddddd045b..5c199e93e71 100644 --- a/internal/service/logs/subscription_filter.go +++ b/internal/service/logs/subscription_filter.go @@ -13,7 +13,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" - "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/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" @@ -23,12 +23,13 @@ import ( "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_cloudwatch_log_subscription_filter") +// @SDKResource("aws_cloudwatch_log_subscription_filter", name="Subscription Filter") func resourceSubscriptionFilter() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceSubscriptionFilterPut, @@ -50,8 +51,8 @@ func resourceSubscriptionFilter() *schema.Resource { "distribution": { Type: schema.TypeString, Optional: true, - Default: types.DistributionByLogStream, - ValidateDiagFunc: enum.Validate[types.Distribution](), + Default: awstypes.DistributionByLogStream, + ValidateDiagFunc: enum.Validate[awstypes.Distribution](), }, "filter_pattern": { Type: schema.TypeString, @@ -81,7 +82,6 @@ func resourceSubscriptionFilter() *schema.Resource { func resourceSubscriptionFilterPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) logGroupName := d.Get(names.AttrLogGroupName).(string) @@ -94,27 +94,30 @@ func resourceSubscriptionFilterPut(ctx context.Context, d *schema.ResourceData, } if v, ok := d.GetOk("distribution"); ok { - input.Distribution = types.Distribution(v.(string)) + input.Distribution = awstypes.Distribution(v.(string)) } if v, ok := d.GetOk(names.AttrRoleARN); ok { input.RoleArn = aws.String(v.(string)) } - _, err := tfresource.RetryWhen(ctx, 5*time.Minute, + const ( + timeout = 5 * time.Minute + ) + _, err := tfresource.RetryWhen(ctx, timeout, func() (interface{}, error) { return conn.PutSubscriptionFilter(ctx, input) }, func(err error) (bool, error) { - if errs.IsAErrorMessageContains[*types.InvalidParameterException](err, "Could not deliver test message to specified") { + if errs.IsAErrorMessageContains[*awstypes.InvalidParameterException](err, "Could not deliver test message to specified") { return true, err } - if errs.IsAErrorMessageContains[*types.InvalidParameterException](err, "Could not execute the lambda function") { + if errs.IsAErrorMessageContains[*awstypes.InvalidParameterException](err, "Could not execute the lambda function") { return true, err } - if errs.IsAErrorMessageContains[*types.OperationAbortedException](err, "Please try again") { + if errs.IsAErrorMessageContains[*awstypes.OperationAbortedException](err, "Please try again") { return true, err } @@ -125,14 +128,15 @@ func resourceSubscriptionFilterPut(ctx context.Context, d *schema.ResourceData, return sdkdiag.AppendErrorf(diags, "putting CloudWatch Logs Subscription Filter (%s): %s", name, err) } - d.SetId(subscriptionFilterID(logGroupName)) + if d.IsNewResource() { + d.SetId(subscriptionFilterCreateResourceID(logGroupName)) + } return diags } func resourceSubscriptionFilterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) subscriptionFilter, err := findSubscriptionFilterByTwoPartKey(ctx, conn, d.Get(names.AttrLogGroupName).(string), d.Get(names.AttrName).(string)) @@ -159,7 +163,6 @@ func resourceSubscriptionFilterRead(ctx context.Context, d *schema.ResourceData, func resourceSubscriptionFilterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).LogsClient(ctx) log.Printf("[INFO] Deleting CloudWatch Logs Subscription Filter: %s", d.Id()) @@ -168,7 +171,7 @@ func resourceSubscriptionFilterDelete(ctx context.Context, d *schema.ResourceDat LogGroupName: aws.String(d.Get(names.AttrLogGroupName).(string)), }) - if errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return diags } @@ -190,30 +193,48 @@ func resourceSubscriptionFilterImport(d *schema.ResourceData, meta interface{}) d.Set(names.AttrLogGroupName, logGroupName) d.Set(names.AttrName, filterNamePrefix) - d.SetId(subscriptionFilterID(filterNamePrefix)) + d.SetId(subscriptionFilterCreateResourceID(filterNamePrefix)) return []*schema.ResourceData{d}, nil } -func subscriptionFilterID(log_group_name string) string { +func subscriptionFilterCreateResourceID(logGroupName string) string { var buf bytes.Buffer - buf.WriteString(fmt.Sprintf("%s-", log_group_name)) // only one filter allowed per log_group_name at the moment + buf.WriteString(fmt.Sprintf("%s-", logGroupName)) // only one filter allowed per log_group_name at the moment return fmt.Sprintf("cwlsf-%d", create.StringHashcode(buf.String())) } -func findSubscriptionFilterByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, logGroupName, name string) (*types.SubscriptionFilter, error) { - input := &cloudwatchlogs.DescribeSubscriptionFiltersInput{ +func findSubscriptionFilterByTwoPartKey(ctx context.Context, conn *cloudwatchlogs.Client, logGroupName, name string) (*awstypes.SubscriptionFilter, error) { + input := cloudwatchlogs.DescribeSubscriptionFiltersInput{ FilterNamePrefix: aws.String(name), LogGroupName: aws.String(logGroupName), } + return findSubscriptionFilter(ctx, conn, &input, func(v *awstypes.SubscriptionFilter) bool { + return aws.ToString(v.FilterName) == name + }) +} + +func findSubscriptionFilter(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeSubscriptionFiltersInput, filter tfslices.Predicate[*awstypes.SubscriptionFilter]) (*awstypes.SubscriptionFilter, error) { + output, err := findSubscriptionFilters(ctx, conn, input, filter) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findSubscriptionFilters(ctx context.Context, conn *cloudwatchlogs.Client, input *cloudwatchlogs.DescribeSubscriptionFiltersInput, filter tfslices.Predicate[*awstypes.SubscriptionFilter]) ([]awstypes.SubscriptionFilter, error) { + var output []awstypes.SubscriptionFilter + pages := cloudwatchlogs.NewDescribeSubscriptionFiltersPaginator(conn, input) for pages.HasMorePages() { page, err := pages.NextPage(ctx) - if errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, @@ -225,11 +246,11 @@ func findSubscriptionFilterByTwoPartKey(ctx context.Context, conn *cloudwatchlog } for _, v := range page.SubscriptionFilters { - if aws.ToString(v.FilterName) == name { - return &v, nil + if filter(&v) { + output = append(output, v) } } } - return nil, tfresource.NewEmptyResultError(input) + return output, nil } diff --git a/internal/service/logs/subscription_filter_test.go b/internal/service/logs/subscription_filter_test.go index dacbd4e6969..70b81d14c0f 100644 --- a/internal/service/logs/subscription_filter_test.go +++ b/internal/service/logs/subscription_filter_test.go @@ -45,10 +45,11 @@ func TestAccLogsSubscriptionFilter_basic(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateIdFunc: testAccSubscriptionFilterImportStateIDFunc(resourceName), - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSubscriptionFilterImportStateIDFunc(resourceName), + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{names.AttrRoleARN}, }, }, }) @@ -202,10 +203,11 @@ func TestAccLogsSubscriptionFilter_distribution(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateIdFunc: testAccSubscriptionFilterImportStateIDFunc(resourceName), - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSubscriptionFilterImportStateIDFunc(resourceName), + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{names.AttrRoleARN}, }, { Config: testAccSubscriptionFilterConfig_distribution(rName, "ByLogStream"), diff --git a/internal/service/logs/validate.go b/internal/service/logs/validate.go index ee1b018cc56..6dda12d1989 100644 --- a/internal/service/logs/validate.go +++ b/internal/service/logs/validate.go @@ -7,33 +7,8 @@ import ( "fmt" "github.com/YakDriver/regexache" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" ) -func validResourcePolicyDocument(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - // http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutResourcePolicy.html - if len(value) > 5120 || (len(value) == 0) { - errors = append(errors, fmt.Errorf("CloudWatch log resource policy document must be between 1 and 5120 characters.")) - } - if _, err := structure.NormalizeJsonString(v); err != nil { - errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err)) - } - return -} - -func validAccountPolicyDocument(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - // https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutAccountPolicy.html - if len(value) > 30720 || (len(value) == 0) { - errors = append(errors, fmt.Errorf("CloudWatch log account policy document must be between 1 and 30,720 characters.")) - } - if _, err := structure.NormalizeJsonString(v); err != nil { - errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err)) - } - return -} - func validLogGroupName(v interface{}, k string) (ws []string, errors []error) { value := v.(string) @@ -112,3 +87,17 @@ func validLogMetricFilterTransformationName(v interface{}, k string) (ws []strin return } + +func validLogStreamName(v interface{}, k string) (ws []string, errors []error) { // nosemgrep:ci.logs-in-func-name + value := v.(string) + if regexache.MustCompile(`:`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "colons not allowed in %q:", k)) + } + if len(value) < 1 || len(value) > 512 { + errors = append(errors, fmt.Errorf( + "%q must be between 1 and 512 characters: %q", k, value)) + } + + return +} diff --git a/internal/service/logs/validate_test.go b/internal/service/logs/validate_test.go index 2195629818f..3c6dbc464d4 100644 --- a/internal/service/logs/validate_test.go +++ b/internal/service/logs/validate_test.go @@ -152,7 +152,7 @@ func TestValidLogMetricTransformationName(t *testing.T) { } } -func TestValidStreamName(t *testing.T) { +func TestValidLogStreamName(t *testing.T) { t.Parallel() validNames := []string{ @@ -162,7 +162,7 @@ func TestValidStreamName(t *testing.T) { "logstream/1234", } for _, v := range validNames { - _, errors := tflogs.ValidStreamName(v, names.AttrName) + _, errors := tflogs.ValidLogStreamName(v, names.AttrName) if len(errors) != 0 { t.Fatalf("%q should be a valid CloudWatch LogStream name: %q", v, errors) } @@ -174,7 +174,7 @@ func TestValidStreamName(t *testing.T) { "stringwith:colon", } for _, v := range invalidNames { - _, errors := tflogs.ValidStreamName(v, names.AttrName) + _, errors := tflogs.ValidLogStreamName(v, names.AttrName) if len(errors) == 0 { t.Fatalf("%q should be an invalid CloudWatch LogStream name", v) } diff --git a/website/docs/r/cloudwatch_log_index_policy.html.markdown b/website/docs/r/cloudwatch_log_index_policy.html.markdown new file mode 100644 index 00000000000..aca884a5361 --- /dev/null +++ b/website/docs/r/cloudwatch_log_index_policy.html.markdown @@ -0,0 +1,56 @@ +--- +subcategory: "CloudWatch Logs" +layout: "aws" +page_title: "AWS: aws_cloudwatch_log_index_policy" +description: |- + Terraform resource for managing an AWS CloudWatch Logs Index Policy. +--- + +# Resource: aws_cloudwatch_log_index_policy + +Terraform resource for managing an AWS CloudWatch Logs Index Policy. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_cloudwatch_log_group" "example" { + name = "example" +} + +resource "aws_cloudwatch_log_index_policy" "example" { + log_group_name = aws_cloudwatch_log_group.example.name + policy_document = jsonencode({ + Fields = ["eventName"] + }) +} +``` + +## Argument Reference + +The following arguments are required: + +* `log_group_name` - (Required) Log group name to set the policy for. +* `policy_document` - (Required) JSON policy document. This is a JSON formatted string. + +## Attribute Reference + +This resource exports no additional attributes. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import CloudWatch Logs Index Policy using the `log_group_name`. For example: + +```terraform +import { + to = aws_cloudwatch_log_index_policy.example + id = "/aws/log/group/name" +} +``` + +Using `terraform import`, import CloudWatch Logs Index Policy using the `log_group_name`. For example: + +```console +% terraform import aws_cloudwatch_log_index_policy.example /aws/log/group/name +```