From 1b6c3dd9d329a616effd3950ec2feeae8fae8dad Mon Sep 17 00:00:00 2001 From: Arthur Hemery Date: Mon, 18 Nov 2024 12:44:05 +0100 Subject: [PATCH] [datadog_rum_metric] Add support for the resource. (#2643) * generate boilerplate * Some fixes on the boilerplate * Tests + fixes * Working base version * Base working version * Add cassettes * Doc fixes * Fix cassettes url * More tests * Update doc * Remove comment about 400 --------- Co-authored-by: Sherzod Karimov --- datadog/fwprovider/framework_provider.go | 1 + .../fwprovider/resource_datadog_rum_metric.go | 418 +++++++++++ .../internal/utils/api_instances_helper.go | 9 + .../TestAccRumMetricAttributes.freeze | 1 + .../cassettes/TestAccRumMetricAttributes.yaml | 709 ++++++++++++++++++ .../cassettes/TestAccRumMetricImport.freeze | 1 + .../cassettes/TestAccRumMetricImport.yaml | 136 ++++ datadog/tests/provider_test.go | 1 + .../tests/resource_datadog_rum_metric_test.go | 331 ++++++++ docs/resources/rum_metric.md | 101 +++ .../resources/datadog_rum_metric/import.sh | 1 + .../resources/datadog_rum_metric/resource.tf | 21 + 12 files changed, 1730 insertions(+) create mode 100644 datadog/fwprovider/resource_datadog_rum_metric.go create mode 100644 datadog/tests/cassettes/TestAccRumMetricAttributes.freeze create mode 100644 datadog/tests/cassettes/TestAccRumMetricAttributes.yaml create mode 100644 datadog/tests/cassettes/TestAccRumMetricImport.freeze create mode 100644 datadog/tests/cassettes/TestAccRumMetricImport.yaml create mode 100644 datadog/tests/resource_datadog_rum_metric_test.go create mode 100644 docs/resources/rum_metric.md create mode 100644 examples/resources/datadog_rum_metric/import.sh create mode 100644 examples/resources/datadog_rum_metric/resource.tf diff --git a/datadog/fwprovider/framework_provider.go b/datadog/fwprovider/framework_provider.go index 5a373b798..34d7886ca 100644 --- a/datadog/fwprovider/framework_provider.go +++ b/datadog/fwprovider/framework_provider.go @@ -52,6 +52,7 @@ var Resources = []func() resource.Resource{ NewIpAllowListResource, NewRestrictionPolicyResource, NewRumApplicationResource, + NewRumMetricResource, NewSensitiveDataScannerGroupOrder, NewServiceAccountApplicationKeyResource, NewSpansMetricResource, diff --git a/datadog/fwprovider/resource_datadog_rum_metric.go b/datadog/fwprovider/resource_datadog_rum_metric.go new file mode 100644 index 000000000..979a040c5 --- /dev/null +++ b/datadog/fwprovider/resource_datadog_rum_metric.go @@ -0,0 +1,418 @@ +package fwprovider + +import ( + "context" + + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" + "github.com/hashicorp/terraform-plugin-framework/diag" + frameworkPath "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/terraform-providers/terraform-provider-datadog/datadog/internal/utils" +) + +var ( + _ resource.ResourceWithConfigure = &rumMetricResource{} + _ resource.ResourceWithImportState = &rumMetricResource{} +) + +type rumMetricResource struct { + Api *datadogV2.RumMetricsApi + Auth context.Context +} + +type rumMetricModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + EventType types.String `tfsdk:"event_type"` + GroupBy []*rumMetricGroupByModel `tfsdk:"group_by"` + Compute *rumMetricComputeModel `tfsdk:"compute"` + Filter *rumMetricFilterModel `tfsdk:"filter"` + Uniqueness *rumMetricUniquenessModel `tfsdk:"uniqueness"` +} + +type rumMetricGroupByModel struct { + Path types.String `tfsdk:"path"` + TagName types.String `tfsdk:"tag_name"` +} + +type rumMetricComputeModel struct { + AggregationType types.String `tfsdk:"aggregation_type"` + IncludePercentiles types.Bool `tfsdk:"include_percentiles"` + Path types.String `tfsdk:"path"` +} + +type rumMetricFilterModel struct { + Query types.String `tfsdk:"query"` +} + +type rumMetricUniquenessModel struct { + When types.String `tfsdk:"when"` +} + +func NewRumMetricResource() resource.Resource { + return &rumMetricResource{} +} + +func (r *rumMetricResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + providerData, _ := request.ProviderData.(*FrameworkProvider) + r.Api = providerData.DatadogApiInstances.GetRumMetricsApiV2() + r.Auth = providerData.Auth +} + +func (r *rumMetricResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "rum_metric" +} + +func (r *rumMetricResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Description: "Provides a Datadog RumMetric resource. This can be used to create and manage Datadog rum_metric.", + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "The name of the RUM-based metric. This field can't be updated after creation.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "event_type": schema.StringAttribute{ + Description: "The type of RUM events to filter on.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "id": utils.ResourceIDAttribute(), + }, + Blocks: map[string]schema.Block{ + "compute": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "aggregation_type": schema.StringAttribute{ + Description: "The type of aggregation to use.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "include_percentiles": schema.BoolAttribute{ + Description: "Toggle to include or exclude percentile aggregations for distribution metrics. Only present when `aggregation_type` is `distribution`.", + Optional: true, + }, + "path": schema.StringAttribute{ + Description: "The path to the value the RUM-based metric will aggregate on. Only present when `aggregation_type` is `distribution`.", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + }, + "filter": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "query": schema.StringAttribute{ + Description: "The search query. Follows RUM search syntax.", + Optional: true, + }, + }, + }, + "group_by": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "path": schema.StringAttribute{ + Description: "The path to the value the RUM-based metric will be aggregated over.", + Optional: true, + }, + "tag_name": schema.StringAttribute{ + Description: "Name of the tag that gets created. By default, `path` is used as the tag name.", + Optional: true, + }, + }, + }, + }, + "uniqueness": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "when": schema.StringAttribute{ + Description: "When to count updatable events. `match` when the event is first seen, or `end` when the event is complete.", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + }, + }, + } +} + +func (r *rumMetricResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, frameworkPath.Root("id"), request, response) +} + +func (r *rumMetricResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var state rumMetricModel + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + id := state.ID.ValueString() + resp, httpResp, err := r.Api.GetRumMetric(r.Auth, id) + if err != nil { + if httpResp != nil && httpResp.StatusCode == 404 { + response.State.RemoveResource(ctx) + return + } + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error retrieving RumMetric")) + return + } + if err := utils.CheckForUnparsed(resp); err != nil { + response.Diagnostics.AddError("response contains unparsedObject", err.Error()) + return + } + + r.updateState(ctx, &state, &resp) + + // Save data into Terraform state + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *rumMetricResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var state rumMetricModel + response.Diagnostics.Append(request.Plan.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + body, diags := r.buildRumMetricRequestBody(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + resp, _, err := r.Api.CreateRumMetric(r.Auth, *body) + if err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error retrieving RumMetric")) + return + } + if err := utils.CheckForUnparsed(resp); err != nil { + response.Diagnostics.AddError("response contains unparsedObject", err.Error()) + return + } + r.updateState(ctx, &state, &resp) + + // Save data into Terraform state + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *rumMetricResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var state rumMetricModel + response.Diagnostics.Append(request.Plan.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + id := state.Name.ValueString() + + body, diags := r.buildRumMetricUpdateRequestBody(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + resp, _, err := r.Api.UpdateRumMetric(r.Auth, id, *body) + if err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error retrieving RumMetric")) + return + } + if err := utils.CheckForUnparsed(resp); err != nil { + response.Diagnostics.AddError("response contains unparsedObject", err.Error()) + return + } + r.updateState(ctx, &state, &resp) + + // Save data into Terraform state + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *rumMetricResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var state rumMetricModel + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + id := state.ID.ValueString() + + httpResp, err := r.Api.DeleteRumMetric(r.Auth, id) + if err != nil { + if httpResp != nil && httpResp.StatusCode == 404 { + return + } + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error deleting rum_metric")) + return + } +} + +func (r *rumMetricResource) updateState(ctx context.Context, state *rumMetricModel, resp *datadogV2.RumMetricResponse) { + state.ID = types.StringValue(resp.Data.GetId()) + state.Name = types.StringValue(resp.Data.GetId()) + + data := resp.GetData() + attributes := data.GetAttributes() + + state.EventType = types.StringValue(string(attributes.GetEventType())) + + if compute, ok := attributes.GetComputeOk(); ok { + + computeTf := rumMetricComputeModel{} + if aggregationType, ok := compute.GetAggregationTypeOk(); ok { + computeTf.AggregationType = types.StringValue(string(*aggregationType)) + } + if includePercentiles, ok := compute.GetIncludePercentilesOk(); ok { + computeTf.IncludePercentiles = types.BoolValue(*includePercentiles) + } + if path, ok := compute.GetPathOk(); ok { + computeTf.Path = types.StringValue(*path) + } + + state.Compute = &computeTf + } + + if filter, ok := attributes.GetFilterOk(); ok { + + filterTf := rumMetricFilterModel{} + if query, ok := filter.GetQueryOk(); ok { + filterTf.Query = types.StringValue(*query) + } + + state.Filter = &filterTf + } + + if groupBy, ok := attributes.GetGroupByOk(); ok && len(*groupBy) > 0 { + state.GroupBy = []*rumMetricGroupByModel{} + for _, groupByDdItem := range *groupBy { + groupByTfItem := rumMetricGroupByModel{} + if path, ok := groupByDdItem.GetPathOk(); ok { + groupByTfItem.Path = types.StringValue(*path) + } + if tagName, ok := groupByDdItem.GetTagNameOk(); ok { + groupByTfItem.TagName = types.StringValue(*tagName) + } + + state.GroupBy = append(state.GroupBy, &groupByTfItem) + } + } + + if uniqueness, ok := attributes.GetUniquenessOk(); ok { + + uniquenessTf := rumMetricUniquenessModel{} + if when, ok := uniqueness.GetWhenOk(); ok { + uniquenessTf.When = types.StringValue(string(*when)) + } + + state.Uniqueness = &uniquenessTf + } +} + +func (r *rumMetricResource) buildRumMetricRequestBody(ctx context.Context, state *rumMetricModel) (*datadogV2.RumMetricCreateRequest, diag.Diagnostics) { + diags := diag.Diagnostics{} + attributes := datadogV2.NewRumMetricCreateAttributesWithDefaults() + + attributes.SetEventType(datadogV2.RumMetricEventType(state.EventType.ValueString())) + + if state.GroupBy != nil { + var groupBy []datadogV2.RumMetricGroupBy + for _, groupByTFItem := range state.GroupBy { + groupByDDItem := datadogV2.NewRumMetricGroupBy(groupByTFItem.Path.ValueString()) + + if !groupByTFItem.TagName.IsNull() { + groupByDDItem.SetTagName(groupByTFItem.TagName.ValueString()) + } + groupBy = append(groupBy, *groupByDDItem) + } + attributes.SetGroupBy(groupBy) + } + + var compute datadogV2.RumMetricCompute + + compute.SetAggregationType(datadogV2.RumMetricComputeAggregationType(state.Compute.AggregationType.ValueString())) + if !state.Compute.IncludePercentiles.IsNull() { + compute.SetIncludePercentiles(state.Compute.IncludePercentiles.ValueBool()) + } + if !state.Compute.Path.IsNull() { + compute.SetPath(state.Compute.Path.ValueString()) + } + + attributes.Compute = compute + + if state.Filter != nil { + var filter datadogV2.RumMetricFilter + + filter.SetQuery(state.Filter.Query.ValueString()) + + attributes.Filter = &filter + } + + if state.Uniqueness != nil { + var uniqueness datadogV2.RumMetricUniqueness + + uniqueness.SetWhen(datadogV2.RumMetricUniquenessWhen(state.Uniqueness.When.ValueString())) + + attributes.Uniqueness = &uniqueness + } + + req := datadogV2.NewRumMetricCreateRequestWithDefaults() + req.Data = *datadogV2.NewRumMetricCreateDataWithDefaults() + req.Data.SetId(state.Name.ValueString()) + req.Data.SetAttributes(*attributes) + + return req, diags +} + +func (r *rumMetricResource) buildRumMetricUpdateRequestBody(ctx context.Context, state *rumMetricModel) (*datadogV2.RumMetricUpdateRequest, diag.Diagnostics) { + diags := diag.Diagnostics{} + attributes := datadogV2.NewRumMetricUpdateAttributesWithDefaults() + + if state.GroupBy != nil { + var groupBy []datadogV2.RumMetricGroupBy + for _, groupByTFItem := range state.GroupBy { + groupByDDItem := datadogV2.NewRumMetricGroupBy(groupByTFItem.Path.ValueString()) + + if !groupByTFItem.TagName.IsNull() { + groupByDDItem.SetTagName(groupByTFItem.TagName.ValueString()) + } + + groupBy = append(groupBy, *groupByDDItem) + } + attributes.SetGroupBy(groupBy) + } + + if state.Compute != nil { + var compute datadogV2.RumMetricUpdateCompute + + if !state.Compute.IncludePercentiles.IsNull() { + compute.SetIncludePercentiles(state.Compute.IncludePercentiles.ValueBool()) + } + + attributes.Compute = &compute + } + + if state.Filter != nil { + var filter datadogV2.RumMetricFilter + + filter.SetQuery(state.Filter.Query.ValueString()) + + attributes.Filter = &filter + } + + req := datadogV2.NewRumMetricUpdateRequestWithDefaults() + req.Data = *datadogV2.NewRumMetricUpdateDataWithDefaults() + req.Data.SetId(state.Name.ValueString()) + req.Data.SetAttributes(*attributes) + + return req, diags +} diff --git a/datadog/internal/utils/api_instances_helper.go b/datadog/internal/utils/api_instances_helper.go index 4dfd68451..7079f1d06 100644 --- a/datadog/internal/utils/api_instances_helper.go +++ b/datadog/internal/utils/api_instances_helper.go @@ -76,6 +76,7 @@ type ApiInstances struct { restrictionPolicyApiV2 *datadogV2.RestrictionPoliciesApi rolesApiV2 *datadogV2.RolesApi rumApiV2 *datadogV2.RUMApi + rumMetricsApiV2 *datadogV2.RumMetricsApi securityMonitoringApiV2 *datadogV2.SecurityMonitoringApi sensitiveDataScannerApiV2 *datadogV2.SensitiveDataScannerApi serviceAccountsApiV2 *datadogV2.ServiceAccountsApi @@ -533,6 +534,14 @@ func (i *ApiInstances) GetRumApiV2() *datadogV2.RUMApi { return i.rumApiV2 } +// GetRumMetricsApiV2 get instance of RumMetricsApi +func (i *ApiInstances) GetRumMetricsApiV2() *datadogV2.RumMetricsApi { + if i.rumMetricsApiV2 == nil { + i.rumMetricsApiV2 = datadogV2.NewRumMetricsApi(i.HttpClient) + } + return i.rumMetricsApiV2 +} + // GetSecurityMonitoringApiV2 get instance of SecurityMonitoringApi func (i *ApiInstances) GetSecurityMonitoringApiV2() *datadogV2.SecurityMonitoringApi { if i.securityMonitoringApiV2 == nil { diff --git a/datadog/tests/cassettes/TestAccRumMetricAttributes.freeze b/datadog/tests/cassettes/TestAccRumMetricAttributes.freeze new file mode 100644 index 000000000..40c5aca43 --- /dev/null +++ b/datadog/tests/cassettes/TestAccRumMetricAttributes.freeze @@ -0,0 +1 @@ +2024-11-14T08:43:39.339296+01:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestAccRumMetricAttributes.yaml b/datadog/tests/cassettes/TestAccRumMetricAttributes.yaml new file mode 100644 index 000000000..87e24a54d --- /dev/null +++ b/datadog/tests/cassettes/TestAccRumMetricAttributes.yaml @@ -0,0 +1,709 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 162 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"compute":{"aggregation_type":"count"},"event_type":"action"},"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 175 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 201 Created + code: 201 + duration: 284.684291ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 175 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 110.622375ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - '*/*' + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: {} + status: 204 No Content + code: 204 + duration: 106.971625ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 215 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"compute":{"aggregation_type":"distribution","include_percentiles":true,"path":"@duration"},"event_type":"action"},"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 228 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"distribution","include_percentiles":true,"path":"@duration"},"event_type":"action","group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 201 Created + code: 201 + duration: 169.534458ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 228 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"distribution","include_percentiles":true,"path":"@duration"},"event_type":"action","group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 101.855583ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 228 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"distribution","include_percentiles":true,"path":"@duration"},"event_type":"action","group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 103.168042ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 141 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"compute":{"include_percentiles":false}},"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: PATCH + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 229 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"distribution","include_percentiles":false,"path":"@duration"},"event_type":"action","group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 198.737833ms + - id: 7 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 229 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"distribution","include_percentiles":false,"path":"@duration"},"event_type":"action","group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 105.835417ms + - id: 8 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - '*/*' + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: {} + status: 204 No Content + code: 204 + duration: 103.626958ms + - id: 9 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 199 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","filter":{"query":"@service:web-ui"}},"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 212 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","filter":{"query":"@service:web-ui"},"group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 201 Created + code: 201 + duration: 295.008292ms + - id: 10 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 212 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","filter":{"query":"@service:web-ui"},"group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 103.364625ms + - id: 11 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 212 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","filter":{"query":"@service:web-ui"},"group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 102.999625ms + - id: 12 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 160 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"compute":{},"filter":{"query":"@service:another-service"}},"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: PATCH + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 221 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","filter":{"query":"@service:another-service"},"group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 113.463959ms + - id: 13 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 221 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","filter":{"query":"@service:another-service"},"group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 105.07ms + - id: 14 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - '*/*' + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: {} + status: 204 No Content + code: 204 + duration: 105.244208ms + - id: 15 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 247 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","group_by":[{"path":"@os","tag_name":"os"},{"path":"@service","tag_name":"service"}]},"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 246 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","group_by":[{"path":"@os","tag_name":"os"},{"path":"@service","tag_name":"service"}]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 201 Created + code: 201 + duration: 173.423208ms + - id: 16 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 246 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","group_by":[{"path":"@os","tag_name":"os"},{"path":"@service","tag_name":"service"}]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 101.0235ms + - id: 17 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 246 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","group_by":[{"path":"@os","tag_name":"os"},{"path":"@service","tag_name":"service"}]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 103.437333ms + - id: 18 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 276 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"compute":{},"group_by":[{"path":"@os","tag_name":"os"},{"path":"@os.version.major","tag_name":"os_version_major"},{"path":"@os.version.minor","tag_name":"os_version_minor"}]},"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: PATCH + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 323 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","group_by":[{"path":"@os","tag_name":"os"},{"path":"@os.version.major","tag_name":"os_version_major"},{"path":"@os.version.minor","tag_name":"os_version_minor"}]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 167.486542ms + - id: 19 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 323 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricAttributes_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","group_by":[{"path":"@os","tag_name":"os"},{"path":"@os.version.major","tag_name":"os_version_major"},{"path":"@os.version.minor","tag_name":"os_version_minor"}]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 102.668125ms + - id: 20 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - '*/*' + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricAttributes_local_1731570219 + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: {} + status: 204 No Content + code: 204 + duration: 105.208958ms diff --git a/datadog/tests/cassettes/TestAccRumMetricImport.freeze b/datadog/tests/cassettes/TestAccRumMetricImport.freeze new file mode 100644 index 000000000..978a2cd7d --- /dev/null +++ b/datadog/tests/cassettes/TestAccRumMetricImport.freeze @@ -0,0 +1 @@ +2024-11-14T08:43:39.33944+01:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestAccRumMetricImport.yaml b/datadog/tests/cassettes/TestAccRumMetricImport.yaml new file mode 100644 index 000000000..99a541846 --- /dev/null +++ b/datadog/tests/cassettes/TestAccRumMetricImport.yaml @@ -0,0 +1,136 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 158 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"data":{"attributes":{"compute":{"aggregation_type":"count"},"event_type":"action"},"id":"tf_TestAccRumMetricImport_local_1731570219","type":"rum_metrics"}} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 171 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricImport_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 201 Created + code: 201 + duration: 303.473833ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricImport_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 171 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricImport_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 120.268708ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricImport_local_1731570219 + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 171 + uncompressed: false + body: '{"data":{"id":"tf_TestAccRumMetricImport_local_1731570219","type":"rum_metrics","attributes":{"compute":{"aggregation_type":"count"},"event_type":"action","group_by":[]}}}' + headers: + Content-Type: + - application/vnd.api+json + status: 200 OK + code: 200 + duration: 107.598417ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - '*/*' + url: https://api.datadoghq.com/api/v2/rum/config/metrics/tf_TestAccRumMetricImport_local_1731570219 + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 0 + uncompressed: false + body: "" + headers: {} + status: 204 No Content + code: 204 + duration: 107.622958ms diff --git a/datadog/tests/provider_test.go b/datadog/tests/provider_test.go index 04516b881..d5f3a28f0 100644 --- a/datadog/tests/provider_test.go +++ b/datadog/tests/provider_test.go @@ -214,6 +214,7 @@ var testFiles2EndpointTags = map[string]string{ "tests/resource_datadog_restriction_policy_test": "restriction-policy", "tests/resource_datadog_role_test": "roles", "tests/resource_datadog_rum_application_test": "rum-application", + "tests/resource_datadog_rum_metric_test": "rum-metric", "tests/resource_datadog_screenboard_test": "dashboards", "tests/resource_datadog_security_monitoring_default_rule_test": "security-monitoring", "tests/resource_datadog_security_monitoring_filter_test": "security-monitoring", diff --git a/datadog/tests/resource_datadog_rum_metric_test.go b/datadog/tests/resource_datadog_rum_metric_test.go new file mode 100644 index 000000000..39db1ae4e --- /dev/null +++ b/datadog/tests/resource_datadog_rum_metric_test.go @@ -0,0 +1,331 @@ +package test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/terraform-providers/terraform-provider-datadog/datadog/fwprovider" + "github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils" +) + +func TestAccRumMetricImport(t *testing.T) { + t.Parallel() + resourceName := "datadog_rum_metric.testing_rum_metric" + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + + // The ID needs to be a valid metric name, otherwise it will get rejected with a 400. + // uniqueEntityName() returns dash separated words, which is not valid, so we replace + // them with underscores. + uniq := strings.ReplaceAll(uniqueEntityName(ctx, t), "-", "_") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogRumMetricDestroy(providers.frameworkProvider), + Steps: []resource.TestStep{ + { + Config: minimalDatadogRumMetric(uniq), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccRumMetricAttributes(t *testing.T) { + t.Parallel() + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + uniq := strings.ReplaceAll(uniqueEntityName(ctx, t), "-", "_") + + resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogRumMetricDestroy(providers.frameworkProvider), + Steps: []resource.TestStep{ + { + Config: minimalDatadogRumMetric(uniq), + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogRumMetricExists(providers.frameworkProvider), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "id", uniq), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "name", uniq), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "event_type", "action"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "compute.aggregation_type", "count"), + ), + }, + }, + }) + + resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogRumMetricDestroy(providers.frameworkProvider), + Steps: []resource.TestStep{ + { + Config: distributionDatadogRumMetric(uniq), + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogRumMetricExists(providers.frameworkProvider), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "compute.aggregation_type", "distribution"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "compute.include_percentiles", "true"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "compute.path", "@duration"), + ), + }, + { + Config: distributionDatadogRumMetricUpdate(uniq), + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogSpansMetricExists(providers.frameworkProvider), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "compute.include_percentiles", "false"), + ), + }, + }, + }) + + resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogRumMetricDestroy(providers.frameworkProvider), + Steps: []resource.TestStep{ + { + Config: filterDatadogRumMetric(uniq), + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogRumMetricExists(providers.frameworkProvider), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "filter.query", "@service:web-ui"), + ), + }, + { + Config: filterDatadogRumMetricUpdate(uniq), + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogRumMetricExists(providers.frameworkProvider), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "filter.query", "@service:another-service"), + ), + }, + }, + }) + + resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogRumMetricDestroy(providers.frameworkProvider), + Steps: []resource.TestStep{ + { + Config: groupByDatadogRumMetric(uniq), + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogRumMetricExists(providers.frameworkProvider), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.#", "2"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.0.path", "@os"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.0.tag_name", "os"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.1.path", "@service"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.1.tag_name", "service"), + ), + }, + { + Config: groupByDatadogRumMetricUpdate(uniq), + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogRumMetricExists(providers.frameworkProvider), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.#", "3"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.0.path", "@os"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.0.tag_name", "os"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.1.path", "@os.version.major"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.1.tag_name", "os_version_major"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.2.path", "@os.version.minor"), + resource.TestCheckResourceAttr( + "datadog_rum_metric.testing_rum_metric", "group_by.2.tag_name", "os_version_minor"), + ), + }, + }, + }) +} + +func minimalDatadogRumMetric(uniq string) string { + return fmt.Sprintf(`resource "datadog_rum_metric" "testing_rum_metric" { + name = %q + event_type = "action" + compute { + aggregation_type = "count" + } + } + `, uniq) +} + +func distributionDatadogRumMetric(uniq string) string { + return fmt.Sprintf(`resource "datadog_rum_metric" "testing_rum_metric" { + name = %q + event_type = "action" + compute { + aggregation_type = "distribution" + include_percentiles = true + path = "@duration" + } + } + `, uniq) +} + +func distributionDatadogRumMetricUpdate(uniq string) string { + return fmt.Sprintf(`resource "datadog_rum_metric" "testing_rum_metric" { + name = %q + event_type = "action" + compute { + aggregation_type = "distribution" + include_percentiles = false + path = "@duration" + } + } + `, uniq) +} + +func filterDatadogRumMetric(uniq string) string { + return fmt.Sprintf(`resource "datadog_rum_metric" "testing_rum_metric" { + name = %q + event_type = "action" + compute { + aggregation_type = "count" + } + filter { + query = "@service:web-ui" + } + } + `, uniq) +} + +func filterDatadogRumMetricUpdate(uniq string) string { + return fmt.Sprintf(`resource "datadog_rum_metric" "testing_rum_metric" { + name = %q + event_type = "action" + compute { + aggregation_type = "count" + } + filter { + query = "@service:another-service" + } + } + `, uniq) +} + +func groupByDatadogRumMetric(uniq string) string { + // Note: the group_bys are not defined in alphabetical order. This is on purpose to verify + // a set behavior rather than a list behavior on the terraform attribute. + return fmt.Sprintf(`resource "datadog_rum_metric" "testing_rum_metric" { + name = %q + event_type = "action" + compute { + aggregation_type = "count" + } + group_by { + path = "@service" + tag_name = "service" + } + group_by { + path = "@os" + tag_name = "os" + } + } + `, uniq) +} + +func groupByDatadogRumMetricUpdate(uniq string) string { + return fmt.Sprintf(`resource "datadog_rum_metric" "testing_rum_metric" { + name = %q + event_type = "action" + compute { + aggregation_type = "count" + } + group_by { + path = "@os" + tag_name = "os" + } + group_by { + path = "@os.version.major" + tag_name = "os_version_major" + } + group_by { + path = "@os.version.minor" + tag_name = "os_version_minor" + } + } + `, uniq) +} + +func testAccCheckDatadogRumMetricDestroy(accProvider *fwprovider.FrameworkProvider) func(*terraform.State) error { + return func(s *terraform.State) error { + apiInstances := accProvider.DatadogApiInstances + auth := accProvider.Auth + + if err := RumMetricDestroyHelper(auth, s, apiInstances); err != nil { + return err + } + return nil + } +} + +func RumMetricDestroyHelper(auth context.Context, s *terraform.State, apiInstances *utils.ApiInstances) error { + err := utils.Retry(2, 10, func() error { + for _, r := range s.RootModule().Resources { + if r.Type != "resource_datadog_rum_metric" { + continue + } + id := r.Primary.ID + + _, httpResp, err := apiInstances.GetRumMetricsApiV2().GetRumMetric(auth, id) + if err != nil { + if httpResp != nil && httpResp.StatusCode == 404 { + return nil + } + return &utils.RetryableError{Prob: fmt.Sprintf("received an error retrieving RumMetric %s", err)} + } + return &utils.RetryableError{Prob: "RumMetric still exists"} + } + return nil + }) + return err +} + +func testAccCheckDatadogRumMetricExists(accProvider *fwprovider.FrameworkProvider) resource.TestCheckFunc { + return func(s *terraform.State) error { + apiInstances := accProvider.DatadogApiInstances + auth := accProvider.Auth + + if err := rumMetricExistsHelper(auth, s, apiInstances); err != nil { + return err + } + return nil + } +} + +func rumMetricExistsHelper(auth context.Context, s *terraform.State, apiInstances *utils.ApiInstances) error { + for _, r := range s.RootModule().Resources { + if r.Type != "resource_datadog_rum_metric" { + continue + } + id := r.Primary.ID + + _, httpResp, err := apiInstances.GetRumMetricsApiV2().GetRumMetric(auth, id) + if err != nil { + return utils.TranslateClientError(err, httpResp, "error retrieving RumMetric") + } + } + return nil +} diff --git a/docs/resources/rum_metric.md b/docs/resources/rum_metric.md new file mode 100644 index 000000000..40997b787 --- /dev/null +++ b/docs/resources/rum_metric.md @@ -0,0 +1,101 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "datadog_rum_metric Resource - terraform-provider-datadog" +subcategory: "" +description: |- + Provides a Datadog RumMetric resource. This can be used to create and manage Datadog rum_metric. +--- + +# datadog_rum_metric (Resource) + +Provides a Datadog RumMetric resource. This can be used to create and manage Datadog rum_metric. + +## Example Usage + +```terraform +# Create new rum_metric resource + +resource "datadog_rum_metric" "testing_rum_metric" { + name = "testing.rum.metric" + compute { + aggregation_type = "distribution" + include_percentiles = true + path = "@duration" + } + event_type = "session" + filter { + query = "@service:web-ui" + } + group_by { + path = "@browser.name" + tag_name = "browser_name" + } + uniqueness { + when = "match" + } +} +``` + + +## Schema + +### Required + +- `event_type` (String) The type of RUM events to filter on. +- `name` (String) The name of the RUM-based metric. This field can't be updated after creation. + +### Optional + +- `compute` (Block, Optional) (see [below for nested schema](#nestedblock--compute)) +- `filter` (Block, Optional) (see [below for nested schema](#nestedblock--filter)) +- `group_by` (Block Set) (see [below for nested schema](#nestedblock--group_by)) +- `uniqueness` (Block, Optional) (see [below for nested schema](#nestedblock--uniqueness)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `compute` + +Required: + +- `aggregation_type` (String) The type of aggregation to use. + +Optional: + +- `include_percentiles` (Boolean) Toggle to include or exclude percentile aggregations for distribution metrics. Only present when `aggregation_type` is `distribution`. +- `path` (String) The path to the value the RUM-based metric will aggregate on. Only present when `aggregation_type` is `distribution`. + + + +### Nested Schema for `filter` + +Optional: + +- `query` (String) The search query. Follows RUM search syntax. + + + +### Nested Schema for `group_by` + +Optional: + +- `path` (String) The path to the value the RUM-based metric will be aggregated over. +- `tag_name` (String) Name of the tag that gets created. By default, `path` is used as the tag name. + + + +### Nested Schema for `uniqueness` + +Optional: + +- `when` (String) When to count updatable events. `match` when the event is first seen, or `end` when the event is complete. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import datadog_rum_metric.testing_rum_metric "testing.rum.metric" +``` diff --git a/examples/resources/datadog_rum_metric/import.sh b/examples/resources/datadog_rum_metric/import.sh new file mode 100644 index 000000000..9d5d2678c --- /dev/null +++ b/examples/resources/datadog_rum_metric/import.sh @@ -0,0 +1 @@ +terraform import datadog_rum_metric.testing_rum_metric "testing.rum.metric" \ No newline at end of file diff --git a/examples/resources/datadog_rum_metric/resource.tf b/examples/resources/datadog_rum_metric/resource.tf new file mode 100644 index 000000000..610dd623b --- /dev/null +++ b/examples/resources/datadog_rum_metric/resource.tf @@ -0,0 +1,21 @@ +# Create new rum_metric resource + +resource "datadog_rum_metric" "testing_rum_metric" { + name = "testing.rum.metric" + compute { + aggregation_type = "distribution" + include_percentiles = true + path = "@duration" + } + event_type = "session" + filter { + query = "@service:web-ui" + } + group_by { + path = "@browser.name" + tag_name = "browser_name" + } + uniqueness { + when = "match" + } +} \ No newline at end of file