Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F/log anomaly detector resource #40437

Merged
merged 31 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a082b77
Initial Commit
nam054 Nov 20, 2024
6705feb
Remove the files in wrong directory; add correct ones
nam054 Dec 2, 2024
ce6c2e4
export tests
nam054 Dec 2, 2024
3b45371
Add tag test generating file
nam054 Dec 4, 2024
80f1644
Tags test generated
nam054 Dec 4, 2024
3db894c
Fix naming of resource; fix testing files; add documentation
nam054 Dec 4, 2024
47759e1
Rename tag gen file
nam054 Dec 4, 2024
6c00086
Clean up the git mess
nam054 Dec 4, 2024
6c24ba4
More naming generation fixes
nam054 Dec 4, 2024
aefc2f0
Comment cleanup
nam054 Dec 4, 2024
00a38d6
Add correct tag labels
nam054 Dec 4, 2024
0c824cc
Remove unneccessary datasource
nam054 Dec 4, 2024
9d09a98
Linter
nam054 Dec 4, 2024
ffed16a
Rename markdown
nam054 Dec 4, 2024
dafad80
Markdown naming fixes
nam054 Dec 4, 2024
4c16cab
Add changelog
nam054 Dec 4, 2024
738b82c
Merge branch 'main' into f/log-anomaly-detector-resource
nam054 Dec 4, 2024
8355444
Potential linter failure
nam054 Dec 4, 2024
138834e
make gen
nam054 Dec 4, 2024
cf5d573
More linter fixes
nam054 Dec 4, 2024
97f1cc0
Docs naming fix
nam054 Dec 4, 2024
48b6c4e
Spacing fixes
nam054 Dec 4, 2024
9a593db
Add import state to basic test
nam054 Dec 4, 2024
e6cf297
Tag test gen format errors
nam054 Dec 4, 2024
07891b4
Linter - add space
nam054 Dec 4, 2024
095c00b
Add quotations
nam054 Dec 4, 2024
1fa5033
tags generate
nam054 Dec 4, 2024
90bf7cb
Review changes
nam054 Dec 5, 2024
beb4e1c
Add update test
nam054 Dec 6, 2024
824cd81
Add more attr to test in update
nam054 Dec 6, 2024
d9c157c
Linter fixes
nam054 Dec 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/40437.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_cloudwatch_log_anomaly_detector
```
299 changes: 299 additions & 0 deletions internal/service/logs/anomaly_detector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package logs

import (
"context"
"errors"

"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-framework-validators/int64validator"
"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/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"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/enum"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
"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"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @FrameworkResource("aws_cloudwatch_log_anomaly_detector", name="Anomaly Detector")
// @Tags(identifierAttribute="arn")
// @Testing(importStateIdFunc="testAccAnomalyDetectorImportStateIDFunc")
// @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{}

return r, nil
}

const (
ResNameAnomalyDetector = "Anomaly Detector"
)

type resourceAnomalyDetector struct {
framework.ResourceWithConfigure
}

func (r *resourceAnomalyDetector) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "aws_cloudwatch_log_anomaly_detector"
}

func (r *resourceAnomalyDetector) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.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,
Validators: []validator.Int64{
int64validator.AtLeast(7),
int64validator.AtMost(90),
nam054 marked this conversation as resolved.
Show resolved Hide resolved
},
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"detector_name": schema.StringAttribute{
Optional: true,
},
"evaluation_frequency": schema.StringAttribute{
Optional: true,
nam054 marked this conversation as resolved.
Show resolved Hide resolved
Validators: []validator.String{
enum.FrameworkValidate[awstypes.EvaluationFrequency](),
},
},
"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(),
},
},
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() {
return
}

in := &cloudwatchlogs.CreateLogAnomalyDetectorInput{
Tags: getTagsIn(ctx),
}
resp.Diagnostics.Append(flex.Expand(ctx, plan, in)...)

if resp.Diagnostics.HasError() {
return
}

out, err := conn.CreateLogAnomalyDetector(ctx, in)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.Logs, create.ErrActionCreating, ResNameAnomalyDetector, plan.ARN.String(), err),
err.Error(),
)
return
}
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() {
return
}

plan.ARN = flex.StringToFramework(ctx, out.AnomalyDetectorArn)

resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

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() {
return
}

out, err := findLogAnomalyDetectorByARN(ctx, conn, state.ARN.ValueString())
if tfresource.NotFound(err) {
resp.State.RemoveResource(ctx)
nam054 marked this conversation as resolved.
Show resolved Hide resolved
return
}
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.Logs, create.ErrActionSetting, ResNameAnomalyDetector, state.ARN.String(), err),
err.Error(),
)
return
}

resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...)
if resp.Diagnostics.HasError() {
return
}

state.AnomalyVisibilityTime = flex.Int64ToFramework(ctx, out.AnomalyVisibilityTime)

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

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() {
return
}

if !plan.DetectorName.Equal(state.DetectorName) ||
!plan.EvaluationFrequency.Equal(state.EvaluationFrequency) ||
!plan.Enabled.Equal(state.Enabled) {
nam054 marked this conversation as resolved.
Show resolved Hide resolved
in := &cloudwatchlogs.UpdateLogAnomalyDetectorInput{}

resp.Diagnostics.Append(flex.Expand(ctx, plan, in)...)
if resp.Diagnostics.HasError() {
return
}

in.AnomalyDetectorArn = plan.ARN.ValueStringPointer()
in.AnomalyVisibilityTime = plan.AnomalyVisibilityTime.ValueInt64Pointer()

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
}

resp.Diagnostics.Append(flex.Flatten(ctx, out, &plan)...)
if resp.Diagnostics.HasError() {
return
}
}

resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

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() {
return
}

in := &cloudwatchlogs.DeleteLogAnomalyDetectorInput{
AnomalyDetectorArn: state.ARN.ValueStringPointer(),
}

_, 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(),
)
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 *resourceAnomalyDetector) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
r.SetTagsAll(ctx, req, resp)
}

func findLogAnomalyDetectorByARN(ctx context.Context, conn *cloudwatchlogs.Client, arn string) (*cloudwatchlogs.GetLogAnomalyDetectorOutput, error) {
in := &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 nil, err
}

if out == nil {
return nil, tfresource.NewEmptyResultError(in)
}

return out, nil
}

type resourceAnomalyDetectorData struct {
ARN types.String `tfsdk:"arn"`
LogGroupARNList fwtypes.ListValueOf[types.String] `tfsdk:"log_group_arn_list"`
AnomalyVisibilityTime types.Int64 `tfsdk:"anomaly_visibility_time"`
DetectorName types.String `tfsdk:"detector_name"`
Enabled types.Bool `tfsdk:"enabled"`
EvaluationFrequency types.String `tfsdk:"evaluation_frequency"`
nam054 marked this conversation as resolved.
Show resolved Hide resolved
FilterPattern types.String `tfsdk:"filter_pattern"`
KMSKeyID types.String `tfsdk:"kms_key_id"`
Tags tftags.Map `tfsdk:"tags"`
TagsAll tftags.Map `tfsdk:"tags_all"`
}
Loading
Loading