Skip to content

Commit 3a4bf7a

Browse files
committed
Merge remote-tracking branch 'origin/master' into klara/budget-terraform
2 parents 448ac27 + d7bd3e5 commit 3a4bf7a

22 files changed

+1570
-7
lines changed

datadog/fwprovider/framework_provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ var Resources = []func() resource.Resource{
8484
NewWorkflowAutomationResource,
8585
NewAppBuilderAppResource,
8686
NewObservabilitPipelineResource,
87+
NewSecurityMonitoringRuleJSONResource,
8788
NewCostBudgetResource,
8889
}
8990

datadog/fwprovider/resource_datadog_integration_gcp_sts.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type integrationGcpStsModel struct {
4747
IsCspmEnabled types.Bool `tfsdk:"is_cspm_enabled"`
4848
IsSecurityCommandCenterEnabled types.Bool `tfsdk:"is_security_command_center_enabled"`
4949
IsResourceChangeCollectionEnabled types.Bool `tfsdk:"is_resource_change_collection_enabled"`
50+
IsPerProjectQuotaEnabled types.Bool `tfsdk:"is_per_project_quota_enabled"`
5051
ResourceCollectionEnabled types.Bool `tfsdk:"resource_collection_enabled"`
5152
}
5253

@@ -131,6 +132,11 @@ func (r *integrationGcpStsResource) Schema(_ context.Context, _ resource.SchemaR
131132
Optional: true,
132133
Computed: true,
133134
},
135+
"is_per_project_quota_enabled": schema.BoolAttribute{
136+
Description: "When enabled, Datadog includes the `X-Goog-User-Project` header to attribute Google Cloud billing and quota usage to the monitored project instead of the default service account project.",
137+
Optional: true,
138+
Computed: true,
139+
},
134140
"resource_collection_enabled": schema.BoolAttribute{
135141
Description: "When enabled, Datadog scans for all resources in your GCP environment.",
136142
Optional: true,
@@ -329,6 +335,9 @@ func (r *integrationGcpStsResource) updateState(ctx context.Context, state *inte
329335
if isResourceChangeCollectionEnabled, ok := attributes.GetIsResourceChangeCollectionEnabledOk(); ok {
330336
state.IsResourceChangeCollectionEnabled = types.BoolValue(*isResourceChangeCollectionEnabled)
331337
}
338+
if isPerProjectQuotaEnabled, ok := attributes.GetIsPerProjectQuotaEnabledOk(); ok {
339+
state.IsPerProjectQuotaEnabled = types.BoolValue(*isPerProjectQuotaEnabled)
340+
}
332341
if resourceCollectionEnabled, ok := attributes.GetResourceCollectionEnabledOk(); ok {
333342
state.ResourceCollectionEnabled = types.BoolValue(*resourceCollectionEnabled)
334343
}
@@ -383,6 +392,9 @@ func (r *integrationGcpStsResource) buildIntegrationGcpStsRequestBody(ctx contex
383392
if !state.ResourceCollectionEnabled.IsUnknown() {
384393
attributes.SetResourceCollectionEnabled(state.ResourceCollectionEnabled.ValueBool())
385394
}
395+
if !state.IsPerProjectQuotaEnabled.IsUnknown() {
396+
attributes.SetIsPerProjectQuotaEnabled(state.IsPerProjectQuotaEnabled.ValueBool())
397+
}
386398

387399
return attributes, diags
388400
}

datadog/fwprovider/resource_datadog_monitor_notification_rule.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (r *MonitorNotificationRuleResource) Metadata(_ context.Context, request re
6464

6565
func (r *MonitorNotificationRuleResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
6666
response.Schema = schema.Schema{
67-
Description: "Provides a Datadog MonitorNotificationRule resource.",
67+
Description: "Provides a Datadog MonitorNotificationRule resource. *Note: This resource is in Preview. [Request access](https://www.datadoghq.com/product-preview/monitor-notification-rules/).*",
6868
Attributes: map[string]schema.Attribute{
6969
"id": utils.ResourceIDAttribute(),
7070
"name": schema.StringAttribute{
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
package fwprovider
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
8+
"github.com/DataDog/datadog-api-client-go/v2/api/datadogV2"
9+
"github.com/hashicorp/terraform-plugin-framework/path"
10+
"github.com/hashicorp/terraform-plugin-framework/resource"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
12+
"github.com/hashicorp/terraform-plugin-framework/types"
13+
14+
"github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils"
15+
)
16+
17+
var (
18+
_ resource.ResourceWithConfigure = &securityMonitoringRuleJSONResource{}
19+
_ resource.ResourceWithImportState = &securityMonitoringRuleJSONResource{}
20+
)
21+
22+
type securityMonitoringRuleJSONResource struct {
23+
Api *datadogV2.SecurityMonitoringApi
24+
Auth context.Context
25+
}
26+
27+
type securityMonitoringRuleJSONModel struct {
28+
ID types.String `tfsdk:"id"`
29+
JSON types.String `tfsdk:"json"`
30+
}
31+
32+
func NewSecurityMonitoringRuleJSONResource() resource.Resource {
33+
return &securityMonitoringRuleJSONResource{}
34+
}
35+
36+
func (r *securityMonitoringRuleJSONResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
37+
providerData := request.ProviderData.(*FrameworkProvider)
38+
r.Api = providerData.DatadogApiInstances.GetSecurityMonitoringApiV2()
39+
r.Auth = providerData.Auth
40+
}
41+
42+
func (r *securityMonitoringRuleJSONResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
43+
response.TypeName = "security_monitoring_rule_json"
44+
}
45+
46+
func (r *securityMonitoringRuleJSONResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
47+
response.Schema = schema.Schema{
48+
Description: "Provides a Datadog Security Monitoring Rule JSON resource. This can be used to create and manage Datadog security monitoring rules using raw JSON.",
49+
Attributes: map[string]schema.Attribute{
50+
"json": schema.StringAttribute{
51+
Required: true,
52+
Description: "The JSON definition of the Security Monitoring Rule.",
53+
},
54+
"id": utils.ResourceIDAttribute(),
55+
},
56+
}
57+
}
58+
59+
func (r *securityMonitoringRuleJSONResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
60+
resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response)
61+
}
62+
63+
// Helper to recursively filter API response to only user-supplied fields
64+
func filterToUserFields(user interface{}, api interface{}) interface{} {
65+
switch userVal := user.(type) {
66+
case map[string]interface{}:
67+
apiMap, ok := api.(map[string]interface{})
68+
if !ok {
69+
return user
70+
}
71+
filtered := make(map[string]interface{})
72+
for k, v := range userVal {
73+
if apiV, ok := apiMap[k]; ok {
74+
filtered[k] = filterToUserFields(v, apiV)
75+
}
76+
}
77+
return filtered
78+
case []interface{}:
79+
apiArr, ok := api.([]interface{})
80+
if !ok {
81+
return user
82+
}
83+
filteredArr := make([]interface{}, len(userVal))
84+
for i := range userVal {
85+
if i < len(apiArr) {
86+
filteredArr[i] = filterToUserFields(userVal[i], apiArr[i])
87+
} else {
88+
filteredArr[i] = userVal[i]
89+
}
90+
}
91+
return filteredArr
92+
default:
93+
return api
94+
}
95+
}
96+
97+
func (r *securityMonitoringRuleJSONResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
98+
var state securityMonitoringRuleJSONModel
99+
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
100+
if response.Diagnostics.HasError() {
101+
return
102+
}
103+
104+
// Parse user JSON into a map
105+
var userRule map[string]interface{}
106+
if err := json.Unmarshal([]byte(state.JSON.ValueString()), &userRule); err != nil {
107+
response.Diagnostics.AddError("Failed to parse JSON", err.Error())
108+
return
109+
}
110+
111+
// Convert the map to SecurityMonitoringRuleCreatePayload
112+
payload := datadogV2.SecurityMonitoringRuleCreatePayload{}
113+
jsonBytes, err := json.Marshal(userRule)
114+
if err != nil {
115+
response.Diagnostics.AddError("Failed to marshal rule", err.Error())
116+
return
117+
}
118+
if err := json.Unmarshal(jsonBytes, &payload); err != nil {
119+
response.Diagnostics.AddError("Failed to unmarshal to payload", err.Error())
120+
return
121+
}
122+
123+
res, httpResp, err := r.Api.CreateSecurityMonitoringRule(r.Auth, payload)
124+
if err != nil {
125+
response.Diagnostics.Append(utils.FrameworkErrorDiag(utils.TranslateClientError(err, httpResp, "error creating security monitoring rule"), ""))
126+
return
127+
}
128+
if err := utils.CheckForUnparsed(res); err != nil {
129+
response.Diagnostics.AddError("Failed to parse response", err.Error())
130+
return
131+
}
132+
133+
var apiRule map[string]interface{}
134+
if res.SecurityMonitoringStandardRuleResponse != nil {
135+
jsonBytes, err = json.Marshal(res.SecurityMonitoringStandardRuleResponse)
136+
} else if res.SecurityMonitoringSignalRuleResponse != nil {
137+
jsonBytes, err = json.Marshal(res.SecurityMonitoringSignalRuleResponse)
138+
} else {
139+
response.Diagnostics.AddError("Invalid response", "Response did not contain a rule")
140+
return
141+
}
142+
if err != nil {
143+
response.Diagnostics.AddError("Failed to marshal response", err.Error())
144+
return
145+
}
146+
147+
if err := json.Unmarshal(jsonBytes, &apiRule); err != nil {
148+
response.Diagnostics.AddError("Failed to parse response", err.Error())
149+
return
150+
}
151+
152+
// Filter API response to only user-supplied fields
153+
filtered := filterToUserFields(userRule, apiRule)
154+
jsonBytes, err = json.Marshal(filtered)
155+
if err != nil {
156+
response.Diagnostics.AddError("Failed to marshal filtered response", err.Error())
157+
return
158+
}
159+
state.JSON = types.StringValue(string(jsonBytes))
160+
if res.SecurityMonitoringStandardRuleResponse != nil {
161+
state.ID = types.StringValue(res.SecurityMonitoringStandardRuleResponse.GetId())
162+
} else if res.SecurityMonitoringSignalRuleResponse != nil {
163+
state.ID = types.StringValue(res.SecurityMonitoringSignalRuleResponse.GetId())
164+
} else {
165+
response.Diagnostics.AddError("Invalid response", "Response did not contain an ID")
166+
return
167+
}
168+
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
169+
}
170+
171+
func (r *securityMonitoringRuleJSONResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
172+
var state securityMonitoringRuleJSONModel
173+
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
174+
if response.Diagnostics.HasError() {
175+
return
176+
}
177+
178+
res, httpResp, err := r.Api.GetSecurityMonitoringRule(r.Auth, state.ID.ValueString())
179+
if err != nil {
180+
if httpResp != nil && httpResp.StatusCode == http.StatusNotFound {
181+
response.State.RemoveResource(ctx)
182+
return
183+
}
184+
response.Diagnostics.Append(utils.FrameworkErrorDiag(utils.TranslateClientError(err, httpResp, "error reading security monitoring rule"), ""))
185+
return
186+
}
187+
if err := utils.CheckForUnparsed(res); err != nil {
188+
response.Diagnostics.AddError("Failed to parse response", err.Error())
189+
return
190+
}
191+
192+
var userRule map[string]interface{}
193+
if err := json.Unmarshal([]byte(state.JSON.ValueString()), &userRule); err != nil {
194+
response.Diagnostics.AddError("Failed to parse state JSON", err.Error())
195+
return
196+
}
197+
198+
var apiRule map[string]interface{}
199+
var jsonBytes []byte
200+
if res.SecurityMonitoringStandardRuleResponse != nil {
201+
jsonBytes, err = json.Marshal(res.SecurityMonitoringStandardRuleResponse)
202+
} else if res.SecurityMonitoringSignalRuleResponse != nil {
203+
jsonBytes, err = json.Marshal(res.SecurityMonitoringSignalRuleResponse)
204+
} else {
205+
response.Diagnostics.AddError("Invalid response", "Response did not contain a rule")
206+
return
207+
}
208+
if err != nil {
209+
response.Diagnostics.AddError("Failed to marshal response", err.Error())
210+
return
211+
}
212+
213+
if err := json.Unmarshal(jsonBytes, &apiRule); err != nil {
214+
response.Diagnostics.AddError("Failed to parse response", err.Error())
215+
return
216+
}
217+
218+
filtered := filterToUserFields(userRule, apiRule)
219+
jsonBytes, err = json.Marshal(filtered)
220+
if err != nil {
221+
response.Diagnostics.AddError("Failed to marshal filtered response", err.Error())
222+
return
223+
}
224+
state.JSON = types.StringValue(string(jsonBytes))
225+
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
226+
}
227+
228+
func (r *securityMonitoringRuleJSONResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
229+
var state securityMonitoringRuleJSONModel
230+
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
231+
if response.Diagnostics.HasError() {
232+
return
233+
}
234+
235+
var userRule map[string]interface{}
236+
if err := json.Unmarshal([]byte(state.JSON.ValueString()), &userRule); err != nil {
237+
response.Diagnostics.AddError("Failed to parse JSON", err.Error())
238+
return
239+
}
240+
241+
payload := datadogV2.SecurityMonitoringRuleUpdatePayload{}
242+
jsonBytes, err := json.Marshal(userRule)
243+
if err != nil {
244+
response.Diagnostics.AddError("Failed to marshal rule", err.Error())
245+
return
246+
}
247+
if err := json.Unmarshal(jsonBytes, &payload); err != nil {
248+
response.Diagnostics.AddError("Failed to unmarshal to payload", err.Error())
249+
return
250+
}
251+
252+
res, httpResp, err := r.Api.UpdateSecurityMonitoringRule(r.Auth, state.ID.ValueString(), payload)
253+
if err != nil {
254+
response.Diagnostics.Append(utils.FrameworkErrorDiag(utils.TranslateClientError(err, httpResp, "error updating security monitoring rule"), ""))
255+
return
256+
}
257+
if err := utils.CheckForUnparsed(res); err != nil {
258+
response.Diagnostics.AddError("Failed to parse response", err.Error())
259+
return
260+
}
261+
262+
var apiRule map[string]interface{}
263+
if res.SecurityMonitoringStandardRuleResponse != nil {
264+
jsonBytes, err = json.Marshal(res.SecurityMonitoringStandardRuleResponse)
265+
} else if res.SecurityMonitoringSignalRuleResponse != nil {
266+
jsonBytes, err = json.Marshal(res.SecurityMonitoringSignalRuleResponse)
267+
} else {
268+
response.Diagnostics.AddError("Invalid response", "Response did not contain a rule")
269+
return
270+
}
271+
if err != nil {
272+
response.Diagnostics.AddError("Failed to marshal response", err.Error())
273+
return
274+
}
275+
276+
if err := json.Unmarshal(jsonBytes, &apiRule); err != nil {
277+
response.Diagnostics.AddError("Failed to parse response", err.Error())
278+
return
279+
}
280+
281+
filtered := filterToUserFields(userRule, apiRule)
282+
jsonBytes, err = json.Marshal(filtered)
283+
if err != nil {
284+
response.Diagnostics.AddError("Failed to marshal filtered response", err.Error())
285+
return
286+
}
287+
state.JSON = types.StringValue(string(jsonBytes))
288+
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
289+
}
290+
291+
func (r *securityMonitoringRuleJSONResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
292+
var state securityMonitoringRuleJSONModel
293+
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
294+
if response.Diagnostics.HasError() {
295+
return
296+
}
297+
298+
httpResp, err := r.Api.DeleteSecurityMonitoringRule(r.Auth, state.ID.ValueString())
299+
if err != nil {
300+
if httpResp != nil && httpResp.StatusCode == http.StatusNotFound {
301+
return
302+
}
303+
response.Diagnostics.Append(utils.FrameworkErrorDiag(utils.TranslateClientError(err, httpResp, "error deleting security monitoring rule"), ""))
304+
return
305+
}
306+
}

0 commit comments

Comments
 (0)