Skip to content

Commit 444c4d1

Browse files
authored
[datadog_monitor_notification_rule] Add support for Monitor Notification Rule (#2980)
* add datadog_monitor_notification_rule resource * Edit description and add set validators
1 parent 319d56a commit 444c4d1

16 files changed

+1008
-3
lines changed

datadog/fwprovider/framework_provider.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ var Resources = []func() resource.Resource{
5252
NewIntegrationGcpResource,
5353
NewIntegrationGcpStsResource,
5454
NewIpAllowListResource,
55+
NewMonitorNotificationRuleResource,
5556
NewSecurityNotificationRuleResource,
5657
NewRestrictionPolicyResource,
5758
NewRumApplicationResource,
@@ -438,6 +439,12 @@ func defaultConfigureFunc(p *FrameworkProvider, request *provider.ConfigureReque
438439
ddClientConfig.SetUnstableOperationEnabled("v2.UpdatePipeline", true)
439440
ddClientConfig.SetUnstableOperationEnabled("v2.DeletePipeline", true)
440441

442+
// Enable MonitorNotificationRule
443+
ddClientConfig.SetUnstableOperationEnabled("v2.CreateMonitorNotificationRule", true)
444+
ddClientConfig.SetUnstableOperationEnabled("v2.GetMonitorNotificationRule", true)
445+
ddClientConfig.SetUnstableOperationEnabled("v2.DeleteMonitorNotificationRule", true)
446+
ddClientConfig.SetUnstableOperationEnabled("v2.UpdateMonitorNotificationRule", true)
447+
441448
if !config.ApiUrl.IsNull() && config.ApiUrl.ValueString() != "" {
442449
parsedAPIURL, parseErr := url.Parse(config.ApiUrl.ValueString())
443450
if parseErr != nil {
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
package fwprovider
2+
3+
import (
4+
"context"
5+
6+
"github.com/DataDog/datadog-api-client-go/v2/api/datadogV2"
7+
"github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
8+
"github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
9+
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
10+
"github.com/hashicorp/terraform-plugin-framework/diag"
11+
frameworkPath "github.com/hashicorp/terraform-plugin-framework/path"
12+
"github.com/hashicorp/terraform-plugin-framework/resource"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
14+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
15+
"github.com/hashicorp/terraform-plugin-framework/types"
16+
17+
"github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils"
18+
)
19+
20+
const resourceType = "monitor-notification-rule"
21+
22+
var (
23+
_ resource.ResourceWithConfigure = &MonitorNotificationRuleResource{}
24+
_ resource.ResourceWithImportState = &MonitorNotificationRuleResource{}
25+
)
26+
27+
type MonitorNotificationRuleResource struct {
28+
Api *datadogV2.MonitorsApi
29+
Auth context.Context
30+
}
31+
32+
type MonitorNotificationRuleModel struct {
33+
ID types.String `tfsdk:"id"`
34+
Name types.String `tfsdk:"name"`
35+
Recipients types.Set `tfsdk:"recipients"`
36+
MonitorNotificationRuleFilter *MonitorNotificationRuleFilter `tfsdk:"filter"`
37+
}
38+
39+
type MonitorNotificationRuleFilter struct {
40+
Tags types.Set `tfsdk:"tags"`
41+
}
42+
43+
func NewMonitorNotificationRuleResource() resource.Resource {
44+
return &MonitorNotificationRuleResource{}
45+
}
46+
47+
func (r *MonitorNotificationRuleResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator {
48+
return []resource.ConfigValidator{
49+
resourcevalidator.ExactlyOneOf(
50+
frameworkPath.MatchRoot("filter").AtName("tags"),
51+
),
52+
}
53+
}
54+
55+
func (r *MonitorNotificationRuleResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
56+
providerData, _ := request.ProviderData.(*FrameworkProvider)
57+
r.Api = providerData.DatadogApiInstances.GetMonitorsApiV2()
58+
r.Auth = providerData.Auth
59+
}
60+
61+
func (r *MonitorNotificationRuleResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
62+
response.TypeName = "monitor_notification_rule"
63+
}
64+
65+
func (r *MonitorNotificationRuleResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
66+
response.Schema = schema.Schema{
67+
Description: "Provides a Datadog MonitorNotificationRule resource.",
68+
Attributes: map[string]schema.Attribute{
69+
"id": utils.ResourceIDAttribute(),
70+
"name": schema.StringAttribute{
71+
Required: true,
72+
Description: "The name of the monitor notification rule.",
73+
},
74+
"recipients": schema.SetAttribute{
75+
Required: true,
76+
ElementType: types.StringType,
77+
Description: "List of recipients to notify.",
78+
Validators: []validator.Set{
79+
setvalidator.SizeAtLeast(1),
80+
},
81+
},
82+
},
83+
Blocks: map[string]schema.Block{
84+
"filter": schema.SingleNestedBlock{
85+
Attributes: map[string]schema.Attribute{
86+
"tags": schema.SetAttribute{
87+
Required: true,
88+
ElementType: types.StringType,
89+
Description: "All tags that target monitors must match.",
90+
Validators: []validator.Set{
91+
setvalidator.SizeAtLeast(1),
92+
},
93+
},
94+
},
95+
Validators: []validator.Object{
96+
objectvalidator.IsRequired(),
97+
},
98+
},
99+
},
100+
}
101+
}
102+
103+
func (r *MonitorNotificationRuleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
104+
resource.ImportStatePassthroughID(ctx, frameworkPath.Root("id"), req, resp)
105+
}
106+
107+
func (r *MonitorNotificationRuleResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
108+
var state MonitorNotificationRuleModel
109+
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
110+
if response.Diagnostics.HasError() {
111+
return
112+
}
113+
id := state.ID.ValueString()
114+
115+
resp, httpResp, err := r.Api.GetMonitorNotificationRule(r.Auth, id)
116+
if err != nil {
117+
if httpResp != nil && httpResp.StatusCode == 404 {
118+
response.State.RemoveResource(ctx)
119+
return
120+
}
121+
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error retrieving MonitorNotificationRule"))
122+
return
123+
}
124+
125+
r.updateState(ctx, &state, &resp)
126+
127+
// Save data into Terraform state
128+
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
129+
}
130+
131+
func (r *MonitorNotificationRuleResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
132+
var state MonitorNotificationRuleModel
133+
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
134+
if response.Diagnostics.HasError() {
135+
return
136+
}
137+
138+
createRequest, diags := r.buildMonitorNotificationRuleCreateRequest(ctx, &state)
139+
response.Diagnostics.Append(diags...)
140+
if response.Diagnostics.HasError() {
141+
return
142+
}
143+
144+
resp, _, err := r.Api.CreateMonitorNotificationRule(r.Auth, *createRequest)
145+
if err != nil {
146+
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error creating MonitorNotificationRule"))
147+
return
148+
}
149+
r.updateState(ctx, &state, &resp)
150+
151+
// Save data into Terraform state
152+
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
153+
}
154+
155+
func (r *MonitorNotificationRuleResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
156+
var state MonitorNotificationRuleModel
157+
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
158+
if response.Diagnostics.HasError() {
159+
return
160+
}
161+
162+
id := state.ID.ValueString()
163+
164+
updateRequest, diags := r.buildMonitorNotificationRuleUpdateRequest(ctx, &state)
165+
response.Diagnostics.Append(diags...)
166+
if response.Diagnostics.HasError() {
167+
return
168+
}
169+
170+
resp, _, err := r.Api.UpdateMonitorNotificationRule(r.Auth, id, *updateRequest)
171+
if err != nil {
172+
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error updating MonitorNotificationRule"))
173+
return
174+
}
175+
r.updateState(ctx, &state, &resp)
176+
177+
// Save data into Terraform state
178+
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
179+
}
180+
181+
func (r *MonitorNotificationRuleResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
182+
var state MonitorNotificationRuleModel
183+
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
184+
if response.Diagnostics.HasError() {
185+
return
186+
}
187+
188+
id := state.ID.ValueString()
189+
190+
httpResp, err := r.Api.DeleteMonitorNotificationRule(r.Auth, id)
191+
if err != nil {
192+
if httpResp != nil && httpResp.StatusCode == 404 {
193+
return
194+
}
195+
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error deleting MonitorNotificationRule"))
196+
return
197+
}
198+
}
199+
200+
func (r *MonitorNotificationRuleResource) updateState(ctx context.Context, state *MonitorNotificationRuleModel, resp *datadogV2.MonitorNotificationRuleResponse) {
201+
state.ID = types.StringValue(resp.Data.GetId())
202+
203+
data := resp.GetData()
204+
attributes := data.GetAttributes()
205+
206+
state.Name = types.StringValue(attributes.GetName())
207+
state.Recipients, _ = types.SetValueFrom(ctx, types.StringType, attributes.GetRecipients())
208+
209+
if filter := attributes.GetFilter(); filter.MonitorNotificationRuleFilterTags != nil {
210+
tags, _ := types.SetValueFrom(ctx, types.StringType, filter.MonitorNotificationRuleFilterTags.GetTags())
211+
state.MonitorNotificationRuleFilter = &MonitorNotificationRuleFilter{
212+
Tags: tags,
213+
}
214+
}
215+
}
216+
217+
func buildRequestAttributes(ctx context.Context, state *MonitorNotificationRuleModel) (*datadogV2.MonitorNotificationRuleAttributes, diag.Diagnostics) {
218+
diags := diag.Diagnostics{}
219+
attributes := datadogV2.NewMonitorNotificationRuleAttributesWithDefaults()
220+
221+
attributes.SetName(state.Name.ValueString())
222+
223+
var recipients []string
224+
diags.Append(state.Recipients.ElementsAs(ctx, &recipients, false)...)
225+
attributes.SetRecipients(recipients)
226+
227+
var tags []string
228+
diags.Append(state.MonitorNotificationRuleFilter.Tags.ElementsAs(ctx, &tags, false)...)
229+
filterTags := datadogV2.NewMonitorNotificationRuleFilterTags(tags)
230+
attributes.SetFilter(datadogV2.MonitorNotificationRuleFilter{MonitorNotificationRuleFilterTags: filterTags})
231+
232+
return attributes, diags
233+
}
234+
235+
func (r *MonitorNotificationRuleResource) buildMonitorNotificationRuleCreateRequest(ctx context.Context, state *MonitorNotificationRuleModel) (*datadogV2.MonitorNotificationRuleCreateRequest, diag.Diagnostics) {
236+
attributes, diags := buildRequestAttributes(ctx, state)
237+
238+
data := datadogV2.NewMonitorNotificationRuleCreateRequestDataWithDefaults()
239+
data.SetType(resourceType)
240+
data.SetAttributes(*attributes)
241+
242+
req := datadogV2.NewMonitorNotificationRuleCreateRequestWithDefaults()
243+
req.SetData(*data)
244+
return req, diags
245+
}
246+
247+
func (r *MonitorNotificationRuleResource) buildMonitorNotificationRuleUpdateRequest(ctx context.Context, state *MonitorNotificationRuleModel) (*datadogV2.MonitorNotificationRuleUpdateRequest, diag.Diagnostics) {
248+
attributes, diags := buildRequestAttributes(ctx, state)
249+
250+
data := datadogV2.NewMonitorNotificationRuleUpdateRequestDataWithDefaults()
251+
data.SetId(state.ID.ValueString())
252+
data.SetType(resourceType)
253+
data.SetAttributes(*attributes)
254+
255+
req := datadogV2.NewMonitorNotificationRuleUpdateRequestWithDefaults()
256+
req.SetData(*data)
257+
return req, diags
258+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2025-04-21T16:24:54.421464-04:00
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
version: 2
3+
interactions:
4+
- id: 0
5+
request:
6+
proto: HTTP/1.1
7+
proto_major: 1
8+
proto_minor: 1
9+
content_length: 228
10+
transfer_encoding: []
11+
trailer: {}
12+
host: api.datadoghq.com
13+
remote_addr: ""
14+
request_uri: ""
15+
body: |
16+
{"data":{"attributes":{"filter":{"tags":["env:tf-TestAccMonitorNotificationRule_Create-local-1745267094","host:abc"]},"name":"A notification rule name","recipients":["jira-bar","slack-foo"]},"type":"monitor-notification-rule"}}
17+
form: {}
18+
headers:
19+
Accept:
20+
- application/json
21+
Content-Type:
22+
- application/json
23+
url: https://api.datadoghq.com/api/v2/monitor/notification_rule
24+
method: POST
25+
response:
26+
proto: HTTP/1.1
27+
proto_major: 1
28+
proto_minor: 1
29+
transfer_encoding:
30+
- chunked
31+
trailer: {}
32+
content_length: -1
33+
uncompressed: true
34+
body: |
35+
{"data":{"type":"monitor-notification-rule","attributes":{"modified_at":"1970-01-01T00:00:00+00:00","filter":{"tags":["env:tf-TestAccMonitorNotificationRule_Create-local-1745267094","host:abc"]},"name":"A notification rule name","created_at":"2025-04-21T20:24:55.453143+00:00","recipients":["jira-bar","slack-foo"]},"id":"ab1a0cab-a31a-4492-bbf3-ffafb5f06248","relationships":{"created_by":{"data":{"type":"users","id":"3ad549bf-eba0-11e9-a77a-0705486660d0"}}}},"included":[{"type":"users","id":"3ad549bf-eba0-11e9-a77a-0705486660d0","attributes":{"name":"frog","handle":"[email protected]","created_at":"2019-10-02T08:15:39.795051+00:00","modified_at":"2025-04-07T20:19:46.118466+00:00","email":"[email protected]","icon":"https://secure.gravatar.com/avatar/28a16dfe36e73b60c1d55872cb0f1172?s=48&d=retro","title":null,"verified":true,"service_account":false,"disabled":false,"allowed_login_methods":[],"status":"Active"}}]}
36+
headers:
37+
Content-Type:
38+
- application/json
39+
status: 200 OK
40+
code: 200
41+
duration: 256.994375ms
42+
- id: 1
43+
request:
44+
proto: HTTP/1.1
45+
proto_major: 1
46+
proto_minor: 1
47+
content_length: 0
48+
transfer_encoding: []
49+
trailer: {}
50+
host: api.datadoghq.com
51+
remote_addr: ""
52+
request_uri: ""
53+
body: ""
54+
form: {}
55+
headers:
56+
Accept:
57+
- application/json
58+
url: https://api.datadoghq.com/api/v2/monitor/notification_rule/ab1a0cab-a31a-4492-bbf3-ffafb5f06248
59+
method: GET
60+
response:
61+
proto: HTTP/1.1
62+
proto_major: 1
63+
proto_minor: 1
64+
transfer_encoding:
65+
- chunked
66+
trailer: {}
67+
content_length: -1
68+
uncompressed: true
69+
body: |
70+
{"data":{"type":"monitor-notification-rule","attributes":{"modified_at":"2025-04-21T20:24:55.488155+00:00","filter":{"tags":["env:tf-TestAccMonitorNotificationRule_Create-local-1745267094","host:abc"]},"name":"A notification rule name","recipients":["jira-bar","slack-foo"],"created_at":"2025-04-21T20:24:55.453144+00:00"},"id":"ab1a0cab-a31a-4492-bbf3-ffafb5f06248"}}
71+
headers:
72+
Content-Type:
73+
- application/json
74+
status: 200 OK
75+
code: 200
76+
duration: 163.347292ms
77+
- id: 2
78+
request:
79+
proto: HTTP/1.1
80+
proto_major: 1
81+
proto_minor: 1
82+
content_length: 0
83+
transfer_encoding: []
84+
trailer: {}
85+
host: api.datadoghq.com
86+
remote_addr: ""
87+
request_uri: ""
88+
body: ""
89+
form: {}
90+
headers:
91+
Accept:
92+
- '*/*'
93+
url: https://api.datadoghq.com/api/v2/monitor/notification_rule/ab1a0cab-a31a-4492-bbf3-ffafb5f06248
94+
method: DELETE
95+
response:
96+
proto: HTTP/1.1
97+
proto_major: 1
98+
proto_minor: 1
99+
transfer_encoding: []
100+
trailer: {}
101+
content_length: 0
102+
uncompressed: false
103+
body: ""
104+
headers:
105+
Content-Type:
106+
- text/html; charset=utf-8
107+
status: 204 No Content
108+
code: 204
109+
duration: 162.676417ms
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2025-04-21T16:24:54.421432-04:00

0 commit comments

Comments
 (0)