diff --git a/datadog/fwprovider/data_source_datadog_rum_application.go b/datadog/fwprovider/data_source_datadog_rum_application.go index 801382b664..f48939e48f 100644 --- a/datadog/fwprovider/data_source_datadog_rum_application.go +++ b/datadog/fwprovider/data_source_datadog_rum_application.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils" ) diff --git a/datadog/fwprovider/framework_provider.go b/datadog/fwprovider/framework_provider.go index 5e1352cdf5..d8f5263640 100644 --- a/datadog/fwprovider/framework_provider.go +++ b/datadog/fwprovider/framework_provider.go @@ -38,6 +38,7 @@ var Resources = []func() resource.Resource{ NewDashboardListResource, NewDowntimeScheduleResource, NewIntegrationAzureResource, + NewIntegrationAwsEventBridgeResource, NewIntegrationCloudflareAccountResource, NewIntegrationConfluentAccountResource, NewIntegrationConfluentResourceResource, diff --git a/datadog/fwprovider/resource_datadog_integration_aws_event_bridge.go b/datadog/fwprovider/resource_datadog_integration_aws_event_bridge.go new file mode 100644 index 0000000000..ad52c645c5 --- /dev/null +++ b/datadog/fwprovider/resource_datadog_integration_aws_event_bridge.go @@ -0,0 +1,257 @@ +package fwprovider + +import ( + "context" + + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV1" + "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/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "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 = &integrationAwsEventBridgeResource{} + _ resource.ResourceWithImportState = &integrationAwsEventBridgeResource{} +) + +type integrationAwsEventBridgeResource struct { + Api *datadogV1.AWSIntegrationApi + Auth context.Context +} + +type integrationAwsEventBridgeModel struct { + ID types.String `tfsdk:"id"` + AccountId types.String `tfsdk:"account_id"` + CreateEventBus types.Bool `tfsdk:"create_event_bus"` + EventGeneratorName types.String `tfsdk:"event_generator_name"` + Region types.String `tfsdk:"region"` +} + +func NewIntegrationAwsEventBridgeResource() resource.Resource { + return &integrationAwsEventBridgeResource{} +} + +func (r *integrationAwsEventBridgeResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + providerData, _ := request.ProviderData.(*FrameworkProvider) + r.Api = providerData.DatadogApiInstances.GetAWSIntegrationApiV1() + r.Auth = providerData.Auth +} + +func (r *integrationAwsEventBridgeResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "integration_aws_event_bridge" +} + +func (r *integrationAwsEventBridgeResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Description: "Provides a Datadog - Amazon Web Services integration EventBridge resource. This can be used to create and manage Event Sources for each Datadog integrated AWS account.", + Attributes: map[string]schema.Attribute{ + "account_id": schema.StringAttribute{ + Description: "Your AWS Account ID without dashes.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "create_event_bus": schema.BoolAttribute{ + Computed: true, + Default: booldefault.StaticBool(true), + Description: "True if Datadog should create the event bus in addition to the event source. Requires the `events:CreateEventBus` permission.", + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "event_generator_name": schema.StringAttribute{ + Description: "The given part of the event source name, which is then combined with an assigned suffix to form the full name.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "region": schema.StringAttribute{ + Description: "The event source's [AWS region](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints).", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "id": utils.ResourceIDAttribute(), + }, + } +} + +func (r *integrationAwsEventBridgeResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, frameworkPath.Root("id"), request, response) +} + +func (r *integrationAwsEventBridgeResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var state integrationAwsEventBridgeModel + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + resp, httpResp, err := r.Api.ListAWSEventBridgeSources(r.Auth) + if err != nil { + if httpResp != nil && httpResp.StatusCode == 404 { + response.State.RemoveResource(ctx) + return + } + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "Error listing AWS EventBridge Event Sources")) + return + } + if err := utils.CheckForUnparsed(resp); err != nil { + response.Diagnostics.AddError("response contains unparsedObject", err.Error()) + return + } + + found := false + matchedEventHub := integrationAwsEventBridgeModel{} + + if accounts, ok := resp.GetAccountsOk(); ok && len(*accounts) > 0 { + for _, account := range *accounts { + if found { + break + } + if eventhubs, ok := account.GetEventHubsOk(); ok && len(*eventhubs) > 0 { + for _, eventhub := range *eventhubs { + if *eventhub.Name == state.ID.ValueString() { + matchedEventHub.ID = types.StringValue(*eventhub.Name) + matchedEventHub.AccountId = types.StringValue(account.GetAccountId()) + matchedEventHub.Region = types.StringValue(eventhub.GetRegion()) + found = true + break + } + } + } + } + } + + if !found { + response.State.RemoveResource(ctx) + response.Diagnostics.AddWarning("Read error", "No matching Event Source found.") + return + } + + r.updateStateAfterRead(ctx, &state, &matchedEventHub) + // Save data into Terraform state + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *integrationAwsEventBridgeResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var state integrationAwsEventBridgeModel + response.Diagnostics.Append(request.Plan.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() + + body, diags := r.buildIntegrationAwsEventBridgeRequestBody(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + resp, _, err := r.Api.CreateAWSEventBridgeSource(r.Auth, *body) + if err != nil { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "Error creating AWS EventBridge Event Source")) + return + } + if err := utils.CheckForUnparsed(resp); err != nil { + response.Diagnostics.AddError("response contains unparsedObject", err.Error()) + return + } + r.updateStateAfterWrite(ctx, &state, &resp) + + // Save data into Terraform state + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +func (r *integrationAwsEventBridgeResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + response.Diagnostics.AddError("Update not supported", "AWS EventBridge Event Sources cannot be updated") +} + +func (r *integrationAwsEventBridgeResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var state integrationAwsEventBridgeModel + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + if response.Diagnostics.HasError() { + return + } + + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() + + req := datadogV1.NewAWSEventBridgeDeleteRequestWithDefaults() + + if !state.AccountId.IsNull() { + req.SetAccountId(state.AccountId.ValueString()) + } + if !state.Region.IsNull() { + req.SetRegion(state.Region.ValueString()) + } + if !state.ID.IsNull() { + // EventGeneratorName in DeleteRequest is the constructed full name, stored as ID in state + req.SetEventGeneratorName(state.ID.ValueString()) + } + + _, httpResp, err := r.Api.DeleteAWSEventBridgeSource(r.Auth, *req) + if err != nil { + if httpResp != nil && httpResp.StatusCode == 404 { + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "AWS EventBridge Event Source not found")) + return + } + response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "Error deleting AWS EventBridge Event Source")) + return + } +} + +func (r *integrationAwsEventBridgeResource) updateStateAfterRead(ctx context.Context, state *integrationAwsEventBridgeModel, resp *integrationAwsEventBridgeModel) { + state.ID = resp.ID + state.AccountId = resp.AccountId + state.Region = resp.Region +} + +func (r *integrationAwsEventBridgeResource) updateStateAfterWrite(ctx context.Context, state *integrationAwsEventBridgeModel, resp *datadogV1.AWSEventBridgeCreateResponse) { + + if createEventBus, ok := resp.GetHasBusOk(); ok { + state.CreateEventBus = types.BoolValue(*createEventBus) + } + + if eventSourceName, ok := resp.GetEventSourceNameOk(); ok { + // Use EventSourceName as ID + state.ID = types.StringValue(*eventSourceName) + } + + if region, ok := resp.GetRegionOk(); ok { + state.Region = types.StringValue(*region) + } +} + +func (r *integrationAwsEventBridgeResource) buildIntegrationAwsEventBridgeRequestBody(ctx context.Context, state *integrationAwsEventBridgeModel) (*datadogV1.AWSEventBridgeCreateRequest, diag.Diagnostics) { + diags := diag.Diagnostics{} + req := datadogV1.NewAWSEventBridgeCreateRequestWithDefaults() + + if !state.AccountId.IsNull() { + req.SetAccountId(state.AccountId.ValueString()) + } + if !state.CreateEventBus.IsNull() { + req.SetCreateEventBus(state.CreateEventBus.ValueBool()) + } + if !state.Region.IsNull() { + req.SetRegion(state.Region.ValueString()) + } + if !state.EventGeneratorName.IsNull() { + req.SetEventGeneratorName(state.EventGeneratorName.ValueString()) + } + + return req, diags +} diff --git a/datadog/internal/utils/utils.go b/datadog/internal/utils/utils.go index f55fc58a4f..5267d47cc4 100644 --- a/datadog/internal/utils/utils.go +++ b/datadog/internal/utils/utils.go @@ -9,6 +9,7 @@ import ( "net/url" "os" "strings" + "sync" "github.com/DataDog/datadog-api-client-go/v2/api/datadog" frameworkDiag "github.com/hashicorp/terraform-plugin-framework/diag" @@ -70,6 +71,9 @@ var APIUrlEnvVars = []string{DDAPIUrlEnvName, DatadogAPIUrlEnvName} // DatadogProvider holds a reference to the provider var DatadogProvider *schema.Provider +// IntegrationAwsMutex mutex for AWS Integration resources +var IntegrationAwsMutex = sync.Mutex{} + // Resource minimal interface common to ResourceData and ResourceDiff type Resource interface { Get(string) interface{} diff --git a/datadog/resource_datadog_integration_aws.go b/datadog/resource_datadog_integration_aws.go index 68beb8c86c..1f88850655 100644 --- a/datadog/resource_datadog_integration_aws.go +++ b/datadog/resource_datadog_integration_aws.go @@ -6,7 +6,6 @@ import ( "os" "regexp" "strconv" - "sync" "github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils" "github.com/terraform-providers/terraform-provider-datadog/datadog/internal/validators" @@ -17,7 +16,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -var integrationAwsMutex = sync.Mutex{} var accountAndRoleNameIDRegex = regexp.MustCompile("[\\d]+:.*") func resourceDatadogIntegrationAws() *schema.Resource { @@ -185,8 +183,8 @@ func resourceDatadogIntegrationAwsCreate(ctx context.Context, d *schema.Resource apiInstances := providerConf.DatadogApiInstances auth := providerConf.Auth - integrationAwsMutex.Lock() - defer integrationAwsMutex.Unlock() + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() iaws := buildDatadogIntegrationAwsStruct(d) response, httpresp, err := apiInstances.GetAWSIntegrationApiV1().CreateAWSAccount(auth, *iaws) @@ -266,8 +264,8 @@ func resourceDatadogIntegrationAwsUpdate(ctx context.Context, d *schema.Resource providerConf := meta.(*ProviderConfiguration) apiInstances := providerConf.DatadogApiInstances auth := providerConf.Auth - integrationAwsMutex.Lock() - defer integrationAwsMutex.Unlock() + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() iaws := buildDatadogIntegrationAwsStruct(d) @@ -323,8 +321,8 @@ func resourceDatadogIntegrationAwsDelete(ctx context.Context, d *schema.Resource providerConf := meta.(*ProviderConfiguration) apiInstances := providerConf.DatadogApiInstances auth := providerConf.Auth - integrationAwsMutex.Lock() - defer integrationAwsMutex.Unlock() + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() iaws := buildDatadogIntegrationAwsDeleteStruct(d) diff --git a/datadog/resource_datadog_integration_aws_lambda_arn.go b/datadog/resource_datadog_integration_aws_lambda_arn.go index df61c4466d..903fc3d92b 100644 --- a/datadog/resource_datadog_integration_aws_lambda_arn.go +++ b/datadog/resource_datadog_integration_aws_lambda_arn.go @@ -56,8 +56,8 @@ func resourceDatadogIntegrationAwsLambdaArnCreate(ctx context.Context, d *schema auth := providerConf.Auth // shared with datadog_integration_aws resource - integrationAwsMutex.Lock() - defer integrationAwsMutex.Unlock() + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() attachLambdaArnRequest := buildDatadogIntegrationAwsLambdaArnStruct(d) response, httpresp, err := apiInstances.GetAWSLogsIntegrationApiV1().CreateAWSLambdaARN(auth, *attachLambdaArnRequest) @@ -121,8 +121,8 @@ func resourceDatadogIntegrationAwsLambdaArnDelete(ctx context.Context, d *schema auth := providerConf.Auth // shared with datadog_integration_aws resource - integrationAwsMutex.Lock() - defer integrationAwsMutex.Unlock() + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() accountID, lambdaArn, err := utils.AccountAndLambdaArnFromID(d.Id()) if err != nil { diff --git a/datadog/resource_datadog_integration_aws_log_collection.go b/datadog/resource_datadog_integration_aws_log_collection.go index 79f72a4dd8..b9f1febae6 100644 --- a/datadog/resource_datadog_integration_aws_log_collection.go +++ b/datadog/resource_datadog_integration_aws_log_collection.go @@ -63,8 +63,8 @@ func resourceDatadogIntegrationAwsLogCollectionCreate(ctx context.Context, d *sc auth := providerConf.Auth // shared with datadog_integration_aws resource - integrationAwsMutex.Lock() - defer integrationAwsMutex.Unlock() + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() accountID := d.Get("account_id").(string) @@ -93,8 +93,8 @@ func resourceDatadogIntegrationAwsLogCollectionUpdate(ctx context.Context, d *sc auth := providerConf.Auth // shared with datadog_integration_aws resource - integrationAwsMutex.Lock() - defer integrationAwsMutex.Unlock() + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() enableLogCollectionServices := buildDatadogIntegrationAwsLogCollectionStruct(d) _, httpresp, err := apiInstances.GetAWSLogsIntegrationApiV1().EnableAWSLogServices(auth, *enableLogCollectionServices) @@ -141,8 +141,8 @@ func resourceDatadogIntegrationAwsLogCollectionDelete(ctx context.Context, d *sc auth := providerConf.Auth // shared with datadog_integration_aws resource - integrationAwsMutex.Lock() - defer integrationAwsMutex.Unlock() + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() accountID := d.Id() services := []string{} diff --git a/datadog/resource_datadog_integration_aws_tag_filter.go b/datadog/resource_datadog_integration_aws_tag_filter.go index 73a4d31bbc..85c5412313 100644 --- a/datadog/resource_datadog_integration_aws_tag_filter.go +++ b/datadog/resource_datadog_integration_aws_tag_filter.go @@ -69,8 +69,8 @@ func resourceDatadogIntegrationAwsTagFilterCreate(ctx context.Context, d *schema providerConf := meta.(*ProviderConfiguration) apiInstances := providerConf.DatadogApiInstances auth := providerConf.Auth - integrationAwsMutex.Lock() - defer integrationAwsMutex.Unlock() + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() req := buildDatadogIntegrationAwsTagFilter(d) if _, httpresp, err := apiInstances.GetAWSIntegrationApiV1().CreateAWSTagFilter(auth, *req); err != nil { @@ -89,8 +89,8 @@ func resourceDatadogIntegrationAwsTagFilterUpdate(ctx context.Context, d *schema providerConf := meta.(*ProviderConfiguration) apiInstances := providerConf.DatadogApiInstances auth := providerConf.Auth - integrationAwsMutex.Lock() - defer integrationAwsMutex.Unlock() + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() req := buildDatadogIntegrationAwsTagFilter(d) if _, httpresp, err := apiInstances.GetAWSIntegrationApiV1().CreateAWSTagFilter(auth, *req); err != nil { @@ -142,8 +142,8 @@ func resourceDatadogIntegrationAwsTagFilterDelete(ctx context.Context, d *schema providerConf := meta.(*ProviderConfiguration) apiInstances := providerConf.DatadogApiInstances auth := providerConf.Auth - integrationAwsMutex.Lock() - defer integrationAwsMutex.Unlock() + utils.IntegrationAwsMutex.Lock() + defer utils.IntegrationAwsMutex.Unlock() accountID, tfNamespace, err := utils.AccountAndNamespaceFromID(d.Id()) if err != nil { diff --git a/datadog/tests/cassettes/TestAccIntegrationAwsEventBridgeBasic.freeze b/datadog/tests/cassettes/TestAccIntegrationAwsEventBridgeBasic.freeze new file mode 100644 index 0000000000..d1e06aa528 --- /dev/null +++ b/datadog/tests/cassettes/TestAccIntegrationAwsEventBridgeBasic.freeze @@ -0,0 +1 @@ +2023-12-22T14:39:55.392655-05:00 \ No newline at end of file diff --git a/datadog/tests/cassettes/TestAccIntegrationAwsEventBridgeBasic.yaml b/datadog/tests/cassettes/TestAccIntegrationAwsEventBridgeBasic.yaml new file mode 100644 index 0000000000..25e6ed5e5e --- /dev/null +++ b/datadog/tests/cassettes/TestAccIntegrationAwsEventBridgeBasic.yaml @@ -0,0 +1,258 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 280 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"account_id":"995450505148","account_specific_namespace_rules":{},"cspm_resource_collection_enabled":false,"excluded_regions":[],"filter_tags":[],"host_tags":[],"metrics_collection_enabled":true,"resource_collection_enabled":false,"role_name":"testacc-datadog-integration-role"} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v1/integration/aws + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: | + {"external_id":"c5ccc77fb96347e28ca60c0cf4a7989e"} + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 877.402042ms + - 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/v1/integration/aws + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: | + {"accounts":[{"account_id":"979910097554","role_name":"1234@testacc-datadog-integration-role","filter_tags":["key:value"],"host_tags":["key:value","key2:value2"],"account_specific_namespace_rules":{"opsworks":true,"auto_scaling":false},"excluded_regions":["us-east-1","us-west-2"],"metrics_collection_enabled":false,"errors":["Datadog is not authorized to perform action sts:AssumeRole\nAccount affected: 979910097554\nRegions affected: every region\nError was last seen 1m ago"],"cspm_resource_collection_enabled":true,"extended_resource_collection_enabled":true,"resource_collection_enabled":true},{"account_id":"571021019953","role_name":"testacc-datadog-integration-role","filter_tags":[],"host_tags":[],"account_specific_namespace_rules":{},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action sts:AssumeRole\nAccount affected: 571021019953\nRegions affected: every region\nError was last seen 1m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"account_id":"123456789188","role_name":"DatadogAWSAcctRoleName","filter_tags":["filter:thisTag"],"host_tags":["env:staging","account:123456"],"account_specific_namespace_rules":{"route53":false,"api_gateway":true},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action sts:AssumeRole\nAccount affected: 123456789188\nRegions affected: every region\nError was last seen 1m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"account_id":"661694029252","role_name":"DatadogAWSIntegrationRole","filter_tags":["testTag","test:Tag2"],"host_tags":["filter:one","filtertwo"],"account_specific_namespace_rules":{"opsworks":true},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action sts:AssumeRole\nAccount affected: 661694029252\nRegions affected: every region\nError was last seen 1m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"account_id":"995450505148","role_name":"testacc-datadog-integration-role","filter_tags":[],"host_tags":[],"account_specific_namespace_rules":{},"excluded_regions":[],"metrics_collection_enabled":true,"errors":[],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"access_key_id":"4949579948531019","filter_tags":[],"host_tags":[],"account_specific_namespace_rules":{},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action cloudwatch:ListMetrics \nAccount affected: 4949579948531019\nRegions affected: every region \nError was last seen 8m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"access_key_id":"1009898975398489","filter_tags":[],"host_tags":[],"account_specific_namespace_rules":{},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action cloudwatch:ListMetrics \nAccount affected: 1009898975398489\nRegions affected: every region \nError was last seen 9m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"access_key_id":"9952495110098101","filter_tags":[],"host_tags":[],"account_specific_namespace_rules":{},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action cloudwatch:ListMetrics \nAccount affected: 9952495110098101\nRegions affected: every region \nError was last seen 9m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false}]} + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 123.5805ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 159 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"account_id":"995450505148","create_event_bus":false,"event_generator_name":"tf-TestAccIntegrationAwsEventBridgeBasic-local-1703273995","region":"us-east-1"} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v1/integration/aws/event_bridge + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: | + {"has_bus":false,"event_source_name":"tf-TestAccIntegrationAwsEventBridgeBasic-local-1703273995-q5m20d6s","status":"created","region":"us-east-1"} + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 624.529458ms + - 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: + - application/json + url: https://api.datadoghq.com/api/v1/integration/aws + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: | + {"accounts":[{"account_id":"979910097554","role_name":"1234@testacc-datadog-integration-role","filter_tags":["key:value"],"host_tags":["key:value","key2:value2"],"account_specific_namespace_rules":{"auto_scaling":false,"opsworks":true},"excluded_regions":["us-east-1","us-west-2"],"metrics_collection_enabled":false,"errors":["Datadog is not authorized to perform action sts:AssumeRole\nAccount affected: 979910097554\nRegions affected: every region\nError was last seen 1m ago"],"cspm_resource_collection_enabled":true,"extended_resource_collection_enabled":true,"resource_collection_enabled":true},{"account_id":"571021019953","role_name":"testacc-datadog-integration-role","filter_tags":[],"host_tags":[],"account_specific_namespace_rules":{},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action sts:AssumeRole\nAccount affected: 571021019953\nRegions affected: every region\nError was last seen 1m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"account_id":"123456789188","role_name":"DatadogAWSAcctRoleName","filter_tags":["filter:thisTag"],"host_tags":["env:staging","account:123456"],"account_specific_namespace_rules":{"api_gateway":true,"route53":false},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action sts:AssumeRole\nAccount affected: 123456789188\nRegions affected: every region\nError was last seen 1m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"account_id":"661694029252","role_name":"DatadogAWSIntegrationRole","filter_tags":["testTag","test:Tag2"],"host_tags":["filter:one","filtertwo"],"account_specific_namespace_rules":{"opsworks":true},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action sts:AssumeRole\nAccount affected: 661694029252\nRegions affected: every region\nError was last seen 1m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"account_id":"995450505148","role_name":"testacc-datadog-integration-role","filter_tags":[],"host_tags":[],"account_specific_namespace_rules":{},"excluded_regions":[],"metrics_collection_enabled":true,"errors":[],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"access_key_id":"4949579948531019","filter_tags":[],"host_tags":[],"account_specific_namespace_rules":{},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action cloudwatch:ListMetrics \nAccount affected: 4949579948531019\nRegions affected: every region \nError was last seen 8m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"access_key_id":"1009898975398489","filter_tags":[],"host_tags":[],"account_specific_namespace_rules":{},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action cloudwatch:ListMetrics \nAccount affected: 1009898975398489\nRegions affected: every region \nError was last seen 9m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false},{"access_key_id":"9952495110098101","filter_tags":[],"host_tags":[],"account_specific_namespace_rules":{},"excluded_regions":[],"metrics_collection_enabled":true,"errors":["Datadog is not authorized to perform action cloudwatch:ListMetrics \nAccount affected: 9952495110098101\nRegions affected: every region \nError was last seen 9m ago"],"cspm_resource_collection_enabled":false,"extended_resource_collection_enabled":false,"resource_collection_enabled":false}]} + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 106.874458ms + - 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/v1/integration/aws/event_bridge + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: | + {"accounts":[{"accountId":"979910097554","tags":["key:value","key2:value2"],"eventHubs":[]},{"accountId":"571021019953","tags":[],"eventHubs":[]},{"accountId":"123456789188","tags":["env:staging","account:123456"],"eventHubs":[]},{"accountId":"661694029252","tags":["filter:one","filtertwo"],"eventHubs":[]},{"accountId":"995450505148","tags":[],"eventHubs":[{"name":"tf-TestAccIntegrationAwsEventBridgeBasic-local-1703273995-q5m20d6s","region":"us-east-1"}]},{"accountId":"494957994853","tags":[],"eventHubs":[]},{"accountId":"100989897539","tags":[],"eventHubs":[]},{"accountId":"995249511009","tags":[],"eventHubs":[]}],"isInstalled":true} + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 69.196792ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 143 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"account_id":"995450505148","event_generator_name":"tf-TestAccIntegrationAwsEventBridgeBasic-local-1703273995-q5m20d6s","region":"us-east-1"} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v1/integration/aws/event_bridge + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 19 + uncompressed: false + body: | + {"status":"empty"} + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 599.64175ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 77 + transfer_encoding: [] + trailer: {} + host: api.datadoghq.com + remote_addr: "" + request_uri: "" + body: | + {"account_id":"995450505148","role_name":"testacc-datadog-integration-role"} + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + url: https://api.datadoghq.com/api/v1/integration/aws + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: [] + trailer: {} + content_length: 3 + uncompressed: false + body: | + {} + headers: + Content-Type: + - application/json + status: 200 OK + code: 200 + duration: 492.987208ms diff --git a/datadog/tests/provider_test.go b/datadog/tests/provider_test.go index 4ca0d65cf4..1ae6f4e789 100644 --- a/datadog/tests/provider_test.go +++ b/datadog/tests/provider_test.go @@ -173,6 +173,7 @@ var testFiles2EndpointTags = map[string]string{ "tests/resource_datadog_integration_aws_log_collection_test": "integration-aws", "tests/resource_datadog_integration_aws_tag_filter_test": "integration-aws", "tests/resource_datadog_integration_aws_test": "integration-aws", + "tests/resource_datadog_integration_aws_event_bridge_test": "integration-aws", "tests/resource_datadog_integration_azure_test": "integration-azure", "tests/resource_datadog_integration_cloudflare_account_test": "integration-cloudflare", "tests/resource_datadog_integration_confluent_account_test": "integration-confluend-account", diff --git a/datadog/tests/resource_datadog_integration_aws_event_bridge_test.go b/datadog/tests/resource_datadog_integration_aws_event_bridge_test.go new file mode 100644 index 0000000000..8ddb25d741 --- /dev/null +++ b/datadog/tests/resource_datadog_integration_aws_event_bridge_test.go @@ -0,0 +1,108 @@ +package test + +import ( + "context" + "fmt" + "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 TestAccIntegrationAwsEventBridgeBasic(t *testing.T) { + t.Parallel() + ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t) + accountID := uniqueAWSAccountID(ctx, t) + uniq := uniqueEntityName(ctx, t) + + resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: accProviders, + CheckDestroy: testAccCheckDatadogIntegrationAwsEventBridgeDestroy(providers.frameworkProvider), + Steps: []resource.TestStep{ + { + Config: testAccCheckDatadogIntegrationAwsEventBridge(accountID, uniq), + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogIntegrationAwsEventBridgeExists(providers.frameworkProvider), + ), + }, + }, + }) +} + +func testAccCheckDatadogIntegrationAwsEventBridge(accountID string, uniq string) string { + return fmt.Sprintf(` + resource "datadog_integration_aws" "account" { + account_id = "%s" + role_name = "testacc-datadog-integration-role" + } + + resource "datadog_integration_aws_event_bridge" "foo" { + account_id = "%s" + event_generator_name = "%s" + create_event_bus = false + region = "us-east-1" + depends_on = [datadog_integration_aws.account] + }`, accountID, accountID, uniq) +} + +func testAccCheckDatadogIntegrationAwsEventBridgeDestroy(accProvider *fwprovider.FrameworkProvider) func(*terraform.State) error { + return func(s *terraform.State) error { + apiInstances := accProvider.DatadogApiInstances + auth := accProvider.Auth + + if err := IntegrationAwsEventBridgeDestroyHelper(auth, s, apiInstances); err != nil { + return err + } + return nil + } +} + +func IntegrationAwsEventBridgeDestroyHelper(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_integration_aws_event_bridge" { + continue + } + + _, httpResp, err := apiInstances.GetAWSIntegrationApiV1().ListAWSEventBridgeSources(auth) + if err != nil { + if httpResp != nil && httpResp.StatusCode == 404 { + return nil + } + return &utils.RetryableError{Prob: fmt.Sprintf("received an error retrieving IntegrationAwsEventBridge %s", err)} + } + return &utils.RetryableError{Prob: "IntegrationAwsEventBridge still exists"} + } + return nil + }) + return err +} + +func testAccCheckDatadogIntegrationAwsEventBridgeExists(accProvider *fwprovider.FrameworkProvider) resource.TestCheckFunc { + return func(s *terraform.State) error { + apiInstances := accProvider.DatadogApiInstances + auth := accProvider.Auth + + if err := integrationAwsEventBridgeExistsHelper(auth, s, apiInstances); err != nil { + return err + } + return nil + } +} + +func integrationAwsEventBridgeExistsHelper(auth context.Context, s *terraform.State, apiInstances *utils.ApiInstances) error { + for _, r := range s.RootModule().Resources { + if r.Type != "resource_datadog_integration_aws_event_bridge" { + continue + } + + _, httpResp, err := apiInstances.GetAWSIntegrationApiV1().ListAWSEventBridgeSources(auth) + if err != nil { + return utils.TranslateClientError(err, httpResp, "error retrieving IntegrationAwsEventBridge") + } + } + return nil +} diff --git a/docs/guides/monitors.md b/docs/guides/monitors.md index beae4248db..76f5be2eee 100644 --- a/docs/guides/monitors.md +++ b/docs/guides/monitors.md @@ -26,9 +26,9 @@ resource "datadog_monitor" "bar" { ```terraform resource "datadog_monitor" "watchdog_monitor" { - name = "Watchdog detected an anomaly: {{event.title}}" - type = "event-v2 alert" - message = "Watchdog monitor created from Terraform" + name = "Watchdog detected an anomaly: {{event.title}}" + type = "event-v2 alert" + message = "Watchdog monitor created from Terraform" query = "events(\"source:watchdog story_category:apm env:test_env\").rollup(\"count\").by(\"story_key,service,resource_name\").last(\"30m\") > 0" diff --git a/docs/resources/integration_aws_event_bridge.md b/docs/resources/integration_aws_event_bridge.md new file mode 100644 index 0000000000..621e7b168d --- /dev/null +++ b/docs/resources/integration_aws_event_bridge.md @@ -0,0 +1,50 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "datadog_integration_aws_event_bridge Resource - terraform-provider-datadog" +subcategory: "" +description: |- + Provides a Datadog - Amazon Web Services integration EventBridge resource. This can be used to create and manage Event Sources for each Datadog integrated AWS account. +--- + +# datadog_integration_aws_event_bridge (Resource) + +Provides a Datadog - Amazon Web Services integration EventBridge resource. This can be used to create and manage Event Sources for each Datadog integrated AWS account. + +## Example Usage + +```terraform +# Create new integration_aws_event_bridge resource + +resource "datadog_integration_aws_event_bridge" "foo" { + account_id = "123456789012" + create_event_bus = true + event_generator_name = "app-alerts" + region = "us-east-1" +} +``` + + +## Schema + +### Required + +- `account_id` (String) Your AWS Account ID without dashes. +- `event_generator_name` (String) The given part of the event source name, which is then combined with an assigned suffix to form the full name. +- `region` (String) The event source's [AWS region](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints). + +### Optional + +- `create_event_bus` (Boolean) True if Datadog should create the event bus in addition to the event source. Requires the `events:CreateEventBus` permission. Defaults to `true`. + +### Read-Only + +- `id` (String) The ID of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +# Amazon Web Service EventBridge integrations are imported using the Event Source name as listed for an integrated AWS account in Datadog +terraform import datadog_integration_aws_event_bridge.foo event-source-name-abc12345 +``` diff --git a/examples/guides/watchdog_monitor.tf b/examples/guides/watchdog_monitor.tf index fc8ef0ce07..32085e0c9e 100644 --- a/examples/guides/watchdog_monitor.tf +++ b/examples/guides/watchdog_monitor.tf @@ -1,7 +1,7 @@ resource "datadog_monitor" "watchdog_monitor" { - name = "Watchdog detected an anomaly: {{event.title}}" - type = "event-v2 alert" - message = "Watchdog monitor created from Terraform" + name = "Watchdog detected an anomaly: {{event.title}}" + type = "event-v2 alert" + message = "Watchdog monitor created from Terraform" query = "events(\"source:watchdog story_category:apm env:test_env\").rollup(\"count\").by(\"story_key,service,resource_name\").last(\"30m\") > 0" diff --git a/examples/resources/datadog_integration_aws_event_bridge/import.sh b/examples/resources/datadog_integration_aws_event_bridge/import.sh new file mode 100644 index 0000000000..f2e868ba59 --- /dev/null +++ b/examples/resources/datadog_integration_aws_event_bridge/import.sh @@ -0,0 +1,2 @@ +# Amazon Web Service EventBridge integrations are imported using the Event Source name as listed for an integrated AWS account in Datadog +terraform import datadog_integration_aws_event_bridge.foo event-source-name-abc12345 diff --git a/examples/resources/datadog_integration_aws_event_bridge/resource.tf b/examples/resources/datadog_integration_aws_event_bridge/resource.tf new file mode 100644 index 0000000000..a8e891a4b2 --- /dev/null +++ b/examples/resources/datadog_integration_aws_event_bridge/resource.tf @@ -0,0 +1,8 @@ +# Create new integration_aws_event_bridge resource + +resource "datadog_integration_aws_event_bridge" "foo" { + account_id = "123456789012" + create_event_bus = true + event_generator_name = "app-alerts" + region = "us-east-1" +}