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

[datadog_apm_retention_filter] Support updating default retention filters #2370

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
103 changes: 99 additions & 4 deletions datadog/fwprovider/resource_datadog_apm_retention_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils"
Expand Down Expand Up @@ -76,7 +77,7 @@ func (r *ApmRetentionFilterResource) Schema(_ context.Context, _ resource.Schema
"filter_type": schema.StringAttribute{
Description: "The type of the retention filter, currently only spans-processing-sampling is available.",
Required: true,
Validators: []validator.String{validators.NewEnumValidator[validator.String](datadogV2.NewRetentionFilterTypeFromValue)},
Validators: []validator.String{validators.NewEnumValidator[validator.String](datadogV2.NewRetentionFilterAllTypeFromValue)},
},
"rate": schema.StringAttribute{
Description: "Sample rate to apply to spans going through this retention filter as a string, a value of 1.0 keeps all spans matching the query.",
Expand Down Expand Up @@ -132,12 +133,73 @@ func (r *ApmRetentionFilterResource) Read(ctx context.Context, request resource.
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

type CommonRequest struct {
Diagnostics diag.Diagnostics
State *tfsdk.State
}

func NewCommonRequest(diag diag.Diagnostics, state *tfsdk.State) CommonRequest {
return CommonRequest{
Diagnostics: diag,
State: state,
}
}

func (r *ApmRetentionFilterResource) getAndUpdate(state *ApmRetentionFilterModel, ctx context.Context, response CommonRequest) {
resp, _, err := r.Api.ListApmRetentionFilters(r.Auth)
if err != nil {
return
}
if err := utils.CheckForUnparsed(resp); err != nil {
return
}
var id string
var filterName string
for _, rfa := range resp.Data {
if string(rfa.Attributes.GetFilterType()) == state.FilterType.ValueString() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we matching my filter type here? Can't we have multiple default pipelines with the same type?

state.ID = types.StringValue(rfa.Id)
id = rfa.Id
filterName = rfa.Attributes.GetName()
break
}
}

body, diags := r.buildApmRetentionFilterUpdateRequestBody(ctx, state)
body.Data.Attributes.SetName(filterName)

response.Diagnostics.Append(diags...)
if response.Diagnostics.HasError() {
return
}

apmRetentionFilterMutex.Lock()
defer apmRetentionFilterMutex.Unlock()

respUpdate, _, err := r.Api.UpdateApmRetentionFilter(r.Auth, id, *body)
if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error dd retention filter"))
return
}
if err := utils.CheckForUnparsed(resp); err != nil {
response.Diagnostics.AddError("response contains unparsedObject", err.Error())
return
}
r.updateState(ctx, state, &respUpdate)

// Save data into Terraform state
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

func (r *ApmRetentionFilterResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
var state ApmRetentionFilterModel
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}
if state.FilterType.ValueString() != "spans-sampling-processor" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can default retention rules not have type spans-sampling-processor ? I see Synthetics Default rule has definition:

                {
                    "id": "",
                    "name": "Synthetics Default",
                    "enabled": false,
                    "meta": {
                        "description": "Get started with Synthetic Monitoring",
                        "tags": [
                            "dd.default:true",
                            "dd.feature:synthetics"
                        ]
                    },
                    "type": "spans-sampling-processor"
                }

r.getAndUpdate(&state, ctx, NewCommonRequest(response.Diagnostics, &response.State))
return
}

body, diags := r.buildRetentionFilterCreateRequestBody(ctx, &state)
response.Diagnostics.Append(diags...)
Expand All @@ -150,14 +212,14 @@ func (r *ApmRetentionFilterResource) Create(ctx context.Context, request resourc

resp, _, err := r.Api.CreateApmRetentionFilter(r.Auth, *body)
if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error retrieving retention filter"))
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error creating retention filter"))
return
}
if err := utils.CheckForUnparsed(resp); err != nil {
response.Diagnostics.AddError("response contains unparsedObject", err.Error())
return
}
r.updateState(ctx, &state, &resp)
r.updateStateForCreate(ctx, &state, &resp)

// Save data into Terraform state
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
Expand All @@ -170,6 +232,11 @@ func (r *ApmRetentionFilterResource) Update(ctx context.Context, request resourc
return
}

if state.FilterType.ValueString() != "spans-sampling-processor" {
r.getAndUpdate(&state, ctx, NewCommonRequest(response.Diagnostics, &response.State))
return
}

id := state.ID.ValueString()

body, diags := r.buildApmRetentionFilterUpdateRequestBody(ctx, &state)
Expand All @@ -183,7 +250,7 @@ func (r *ApmRetentionFilterResource) Update(ctx context.Context, request resourc

resp, _, err := r.Api.UpdateApmRetentionFilter(r.Auth, id, *body)
if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error retrieving retention filter"))
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error updating retention filter"))
return
}
if err := utils.CheckForUnparsed(resp); err != nil {
Expand All @@ -203,6 +270,10 @@ func (r *ApmRetentionFilterResource) Delete(ctx context.Context, request resourc
return
}

// Default filters cannot be deleted, skip the deletion
if state.FilterType.ValueString() != "spans-sampling-processor" {
return
}
id := state.ID.ValueString()

apmRetentionFilterMutex.Lock()
Expand All @@ -219,6 +290,30 @@ func (r *ApmRetentionFilterResource) Delete(ctx context.Context, request resourc
}

func (r *ApmRetentionFilterResource) updateState(ctx context.Context, state *ApmRetentionFilterModel, resp *datadogV2.RetentionFilterResponse) {
state.ID = types.StringValue(resp.Data.GetId())
// Ignore the name if it is a default filter, since it is not editable
if *resp.Data.Attributes.FilterType == datadogV2.RETENTIONFILTERALLTYPE_SPANS_SAMPLING_PROCESSOR {
state.Name = types.StringValue(resp.Data.Attributes.GetName())
}
// Make sure we maintain the same precision as config
// Otherwise we will run into inconsistent state errors
configVal := state.Rate.ValueString()
precision := -1
if i := strings.IndexByte(configVal, '.'); i > -1 {
precision = len(configVal) - i - 1
}
state.Rate = types.StringValue(strconv.FormatFloat(resp.Data.Attributes.GetRate(), 'f', precision, 64))

if state.Filter == nil {
filter := retentionFilterModel{}
state.Filter = &filter
}
state.Filter.Query = types.StringValue(*resp.Data.Attributes.GetFilter().Query)
state.Enabled = types.BoolValue(*resp.Data.Attributes.Enabled)
state.FilterType = types.StringValue(string(resp.Data.Attributes.GetFilterType()))
}

func (r *ApmRetentionFilterResource) updateStateForCreate(ctx context.Context, state *ApmRetentionFilterModel, resp *datadogV2.RetentionFilterCreateResponse) {
state.ID = types.StringValue(resp.Data.GetId())
state.Name = types.StringValue(resp.Data.Attributes.GetName())

Expand Down
2 changes: 1 addition & 1 deletion datadog/tests/cassettes/TestAccApmRetentionFilter.freeze
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2024-01-16T15:28:02.191729-05:00
2024-04-18T16:05:07.003325+02:00
Loading
Loading