Skip to content

Commit fcacf6f

Browse files
update csm_threats_agent_rule resource and data_source
1 parent cf16019 commit fcacf6f

5 files changed

+169
-28
lines changed

datadog/fwprovider/data_source_datadog_csm_threats_agent_rule.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func (r *csmThreatsAgentRulesDataSource) Configure(_ context.Context, request da
6262
r.auth = providerData.Auth
6363
}
6464

65-
func (r *csmThreatsAgentRulesDataSource) Metadata(_ context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) {
65+
func (*csmThreatsAgentRulesDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, response *datasource.MetadataResponse) {
6666
response.TypeName = "csm_threats_agent_rules"
6767
}
6868

datadog/fwprovider/resource_datadog_csm_threats_agent_rule.go

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
1414
"github.com/hashicorp/terraform-plugin-framework/types"
1515

16+
"net/http"
17+
1618
"github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils"
1719
)
1820

@@ -57,7 +59,7 @@ func (r *csmThreatsAgentRuleResource) Schema(_ context.Context, _ resource.Schem
5759
Attributes: map[string]schema.Attribute{
5860
"id": utils.ResourceIDAttribute(),
5961
"policy_id": schema.StringAttribute{
60-
Required: true,
62+
Optional: true,
6163
Description: "The ID of the agent policy in which the rule is saved",
6264
},
6365
"name": schema.StringAttribute{
@@ -70,38 +72,35 @@ func (r *csmThreatsAgentRuleResource) Schema(_ context.Context, _ resource.Schem
7072
"description": schema.StringAttribute{
7173
Optional: true,
7274
Description: "A description for the Agent rule.",
73-
Computed: true,
7475
},
7576
"enabled": schema.BoolAttribute{
76-
Required: true,
77-
Description: "Indicates Whether the Agent rule is enabled.",
77+
Optional: true,
78+
Description: "Indicates whether the Agent rule is enabled. Must not be used without policy_id.",
7879
},
7980
"expression": schema.StringAttribute{
8081
Required: true,
8182
Description: "The SECL expression of the Agent rule",
82-
PlanModifiers: []planmodifier.String{
83-
stringplanmodifier.RequiresReplace(),
84-
},
8583
},
8684
"product_tags": schema.SetAttribute{
8785
Optional: true,
8886
ElementType: types.StringType,
8987
Description: "The list of product tags associated with the rule",
90-
Computed: true,
9188
},
9289
},
9390
}
9491
}
9592

9693
func (r *csmThreatsAgentRuleResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
9794
result := strings.SplitN(request.ID, ":", 2)
98-
if len(result) != 2 {
99-
response.Diagnostics.AddError("error retrieving policy_id or rule_id from given ID", "")
100-
return
101-
}
10295

103-
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("policy_id"), result[0])...)
104-
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("id"), result[1])...)
96+
if len(result) == 2 {
97+
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("policy_id"), result[0])...)
98+
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("id"), result[1])...)
99+
} else if len(result) == 1 {
100+
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("id"), result[0])...)
101+
} else {
102+
response.Diagnostics.AddError("unexpected import format", "expected '<policy_id>:<rule_id>' or '<rule_id>'")
103+
}
105104
}
106105

107106
func (r *csmThreatsAgentRuleResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
@@ -111,6 +110,9 @@ func (r *csmThreatsAgentRuleResource) Create(ctx context.Context, request resour
111110
return
112111
}
113112

113+
csmThreatsMutex.Lock()
114+
defer csmThreatsMutex.Unlock()
115+
114116
agentRulePayload, err := r.buildCreateCSMThreatsAgentRulePayload(&state)
115117
if err != nil {
116118
response.Diagnostics.AddError("error while parsing resource", err.Error())
@@ -137,11 +139,23 @@ func (r *csmThreatsAgentRuleResource) Read(ctx context.Context, request resource
137139
return
138140
}
139141

142+
csmThreatsMutex.Lock()
143+
defer csmThreatsMutex.Unlock()
144+
140145
agentRuleId := state.Id.ValueString()
141-
policyId := state.PolicyId.ValueString()
142-
res, httpResponse, err := r.api.GetCSMThreatsAgentRule(r.auth, agentRuleId, *datadogV2.NewGetCSMThreatsAgentRuleOptionalParameters().WithPolicyId(policyId))
146+
147+
var res datadogV2.CloudWorkloadSecurityAgentRuleResponse
148+
var httpResp *http.Response
149+
var err error
150+
if !state.PolicyId.IsNull() && !state.PolicyId.IsUnknown() {
151+
policyId := state.PolicyId.ValueString()
152+
res, httpResp, err = r.api.GetCSMThreatsAgentRule(r.auth, agentRuleId, *datadogV2.NewGetCSMThreatsAgentRuleOptionalParameters().WithPolicyId(policyId))
153+
} else {
154+
res, httpResp, err = r.api.GetCSMThreatsAgentRule(r.auth, agentRuleId)
155+
}
156+
143157
if err != nil {
144-
if httpResponse != nil && httpResponse.StatusCode == 404 {
158+
if httpResp.StatusCode == 404 {
145159
response.State.RemoveResource(ctx)
146160
return
147161
}
@@ -164,9 +178,13 @@ func (r *csmThreatsAgentRuleResource) Update(ctx context.Context, request resour
164178
return
165179
}
166180

181+
csmThreatsMutex.Lock()
182+
defer csmThreatsMutex.Unlock()
183+
167184
agentRulePayload, err := r.buildUpdateCSMThreatsAgentRulePayload(&state)
168185
if err != nil {
169186
response.Diagnostics.AddError("error while parsing resource", err.Error())
187+
return
170188
}
171189

172190
res, _, err := r.api.UpdateCSMThreatsAgentRule(r.auth, state.Id.ValueString(), *agentRulePayload)
@@ -190,11 +208,22 @@ func (r *csmThreatsAgentRuleResource) Delete(ctx context.Context, request resour
190208
return
191209
}
192210

211+
csmThreatsMutex.Lock()
212+
defer csmThreatsMutex.Unlock()
213+
193214
id := state.Id.ValueString()
194-
policyId := state.PolicyId.ValueString()
195-
httpResp, err := r.api.DeleteCSMThreatsAgentRule(r.auth, id, *datadogV2.NewDeleteCSMThreatsAgentRuleOptionalParameters().WithPolicyId(policyId))
215+
216+
var httpResp *http.Response
217+
var err error
218+
if !state.PolicyId.IsNull() && !state.PolicyId.IsUnknown() {
219+
policyId := state.PolicyId.ValueString()
220+
httpResp, err = r.api.DeleteCSMThreatsAgentRule(r.auth, id, *datadogV2.NewDeleteCSMThreatsAgentRuleOptionalParameters().WithPolicyId(policyId))
221+
} else {
222+
httpResp, err = r.api.DeleteCSMThreatsAgentRule(r.auth, id)
223+
}
224+
196225
if err != nil {
197-
if httpResp != nil && httpResp.StatusCode == 404 {
226+
if httpResp.StatusCode == 404 {
198227
return
199228
}
200229
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error deleting agent rule"))
@@ -210,32 +239,39 @@ func (r *csmThreatsAgentRuleResource) buildCreateCSMThreatsAgentRulePayload(stat
210239
attributes.Name = name
211240
attributes.Description = description
212241
attributes.Enabled = &enabled
213-
attributes.PolicyId = &policyId
242+
attributes.PolicyId = policyId
214243
attributes.ProductTags = productTags
215244

216245
data := datadogV2.NewCloudWorkloadSecurityAgentRuleCreateData(attributes, datadogV2.CLOUDWORKLOADSECURITYAGENTRULETYPE_AGENT_RULE)
217246
return datadogV2.NewCloudWorkloadSecurityAgentRuleCreateRequest(*data), nil
218247
}
219248

220249
func (r *csmThreatsAgentRuleResource) buildUpdateCSMThreatsAgentRulePayload(state *csmThreatsAgentRuleModel) (*datadogV2.CloudWorkloadSecurityAgentRuleUpdateRequest, error) {
221-
agentRuleId, policyId, _, description, enabled, _, productTags := r.extractAgentRuleAttributesFromResource(state)
250+
agentRuleId, policyId, _, description, enabled, expression, productTags := r.extractAgentRuleAttributesFromResource(state)
222251

223252
attributes := datadogV2.CloudWorkloadSecurityAgentRuleUpdateAttributes{}
253+
attributes.Expression = &expression
224254
attributes.Description = description
225255
attributes.Enabled = &enabled
226-
attributes.PolicyId = &policyId
256+
attributes.PolicyId = policyId
227257
attributes.ProductTags = productTags
228258

229259
data := datadogV2.NewCloudWorkloadSecurityAgentRuleUpdateData(attributes, datadogV2.CLOUDWORKLOADSECURITYAGENTRULETYPE_AGENT_RULE)
230260
data.Id = &agentRuleId
231261
return datadogV2.NewCloudWorkloadSecurityAgentRuleUpdateRequest(*data), nil
232262
}
233263

234-
func (r *csmThreatsAgentRuleResource) extractAgentRuleAttributesFromResource(state *csmThreatsAgentRuleModel) (string, string, string, *string, bool, string, []string) {
264+
func (r *csmThreatsAgentRuleResource) extractAgentRuleAttributesFromResource(state *csmThreatsAgentRuleModel) (string, *string, string, *string, bool, string, []string) {
235265
// Mandatory fields
236266
id := state.Id.ValueString()
237-
policyId := state.PolicyId.ValueString()
238267
name := state.Name.ValueString()
268+
269+
// Optional fields
270+
var policyId *string
271+
if !state.PolicyId.IsNull() && !state.PolicyId.IsUnknown() {
272+
val := state.PolicyId.ValueString()
273+
policyId = &val
274+
}
239275
enabled := state.Enabled.ValueBool()
240276
expression := state.Expression.ValueString()
241277
description := state.Description.ValueStringPointer()
@@ -244,7 +280,7 @@ func (r *csmThreatsAgentRuleResource) extractAgentRuleAttributesFromResource(sta
244280
for _, tag := range state.ProductTags.Elements() {
245281
tagStr, ok := tag.(types.String)
246282
if !ok {
247-
return "", "", "", nil, false, "", nil
283+
return "", nil, "", nil, false, "", nil
248284
}
249285
productTags = append(productTags, tagStr.ValueString())
250286
}

datadog/tests/data_source_datadog_csm_threats_agent_rules_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,42 @@ func TestAccCSMThreatsAgentRulesDataSource(t *testing.T) {
6464
})
6565
}
6666

67+
func TestAccCSMThreatsAgentRulesDataSourceWithoutPolicyID(t *testing.T) {
68+
t.Parallel()
69+
ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t)
70+
71+
agentRuleName := uniqueAgentRuleName(ctx)
72+
73+
agentRuleConfig := fmt.Sprintf(`
74+
resource "datadog_csm_threats_agent_rule" "agent_rule_without_policy" {
75+
name = "%s"
76+
enabled = true
77+
description = "rule without policy"
78+
expression = "open.file.name == \"etc/shadow/password\""
79+
product_tags = ["compliance_framework:PCI-DSS"]
80+
}
81+
`, agentRuleName)
82+
83+
resource.Test(t, resource.TestCase{
84+
PreCheck: func() { testAccPreCheck(t) },
85+
ProtoV5ProviderFactories: accProviders,
86+
CheckDestroy: testAccCheckCSMThreatsAgentRuleDestroy(providers.frameworkProvider),
87+
Steps: []resource.TestStep{
88+
{
89+
Config: agentRuleConfig,
90+
Check: resource.ComposeTestCheckFunc(
91+
testAccCheckCSMThreatsAgentRuleExists(providers.frameworkProvider, "datadog_csm_threats_agent_rule.agent_rule_without_policy"),
92+
resource.TestCheckResourceAttr("datadog_csm_threats_agent_rule.agent_rule_without_policy", "name", agentRuleName),
93+
resource.TestCheckResourceAttr("datadog_csm_threats_agent_rule.agent_rule_without_policy", "description", "rule without policy"),
94+
resource.TestCheckResourceAttr("datadog_csm_threats_agent_rule.agent_rule_without_policy", "expression", "open.file.name == \"etc/shadow/password\""),
95+
resource.TestCheckResourceAttr("datadog_csm_threats_agent_rule.agent_rule_without_policy", "enabled", "true"),
96+
resource.TestCheckTypeSetElemAttr("datadog_csm_threats_agent_rule.agent_rule_without_policy", "product_tags.*", "compliance_framework:PCI-DSS"),
97+
),
98+
},
99+
},
100+
})
101+
}
102+
67103
func testAccCheckDatadogCSMThreatsAgentRulesDataSourceConfig(policyName, agentRuleName string) string {
68104
return fmt.Sprintf(`
69105
data "datadog_csm_threats_agent_rules" "my_data_source" {

datadog/tests/resource_datadog_csm_threats_agent_rule_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,52 @@ func TestAccCSMThreatsAgentRule_CreateAndUpdate(t *testing.T) {
9191
})
9292
}
9393

94+
func TestAccCSMThreatsAgentRule_CreateAndUpdateWithoutPolicyID(t *testing.T) {
95+
ctx, providers, accProviders := testAccFrameworkMuxProviders(context.Background(), t)
96+
97+
agentRuleName := uniqueAgentRuleName(ctx)
98+
resourceName := "datadog_csm_threats_agent_rule.agent_rule_without_policy"
99+
100+
resource.Test(t, resource.TestCase{
101+
PreCheck: func() { testAccPreCheck(t) },
102+
ProtoV5ProviderFactories: accProviders,
103+
CheckDestroy: testAccCheckCSMThreatsAgentRuleDestroy(providers.frameworkProvider),
104+
Steps: []resource.TestStep{
105+
{
106+
Config: fmt.Sprintf(`
107+
resource "datadog_csm_threats_agent_rule" "agent_rule_without_policy" {
108+
name = "%s"
109+
enabled = true
110+
description = "initial description"
111+
expression = "open.file.name == \"etc/shadow/password\""
112+
product_tags = ["compliance_framework:HIPAA"]
113+
}
114+
`, agentRuleName),
115+
Check: resource.ComposeTestCheckFunc(
116+
testAccCheckCSMThreatsAgentRuleExistsWithoutPolicyID(providers.frameworkProvider, resourceName),
117+
checkCSMThreatsAgentRuleContent(resourceName, agentRuleName, "initial description", "open.file.name == \"etc/shadow/password\"", "compliance_framework:HIPAA"),
118+
),
119+
},
120+
// update the description
121+
{
122+
Config: fmt.Sprintf(`
123+
resource "datadog_csm_threats_agent_rule" "agent_rule_without_policy" {
124+
name = "%s"
125+
enabled = true
126+
description = "updated description"
127+
expression = "open.file.name == \"etc/shadow/password\""
128+
product_tags = ["compliance_framework:HIPAA"]
129+
}
130+
`, agentRuleName),
131+
Check: resource.ComposeTestCheckFunc(
132+
testAccCheckCSMThreatsAgentRuleExistsWithoutPolicyID(providers.frameworkProvider, resourceName),
133+
checkCSMThreatsAgentRuleContent(resourceName, agentRuleName, "updated description", "open.file.name == \"etc/shadow/password\"", "compliance_framework:HIPAA"),
134+
),
135+
},
136+
},
137+
})
138+
}
139+
94140
func testAccCheckCSMThreatsAgentRuleExists(accProvider *fwprovider.FrameworkProvider, resourceName string) resource.TestCheckFunc {
95141
return func(s *terraform.State) error {
96142
resource, ok := s.RootModule().Resources[resourceName]
@@ -115,6 +161,29 @@ func testAccCheckCSMThreatsAgentRuleExists(accProvider *fwprovider.FrameworkProv
115161
}
116162
}
117163

164+
func testAccCheckCSMThreatsAgentRuleExistsWithoutPolicyID(accProvider *fwprovider.FrameworkProvider, resourceName string) resource.TestCheckFunc {
165+
return func(s *terraform.State) error {
166+
resource, ok := s.RootModule().Resources[resourceName]
167+
if !ok {
168+
return fmt.Errorf("resource '%s' not found in the state", resourceName)
169+
}
170+
171+
if resource.Type != "datadog_csm_threats_agent_rule" {
172+
return fmt.Errorf("resource %s is not of type datadog_csm_threats_agent_rule", resourceName)
173+
}
174+
175+
auth := accProvider.Auth
176+
apiInstances := accProvider.DatadogApiInstances
177+
178+
_, _, err := apiInstances.GetCSMThreatsApiV2().GetCSMThreatsAgentRule(auth, resource.Primary.ID)
179+
if err != nil {
180+
return fmt.Errorf("received an error retrieving agent rule: %s", err)
181+
}
182+
183+
return nil
184+
}
185+
}
186+
118187
func testAccCheckCSMThreatsAgentRuleDestroy(accProvider *fwprovider.FrameworkProvider) resource.TestCheckFunc {
119188
return func(s *terraform.State) error {
120189
auth := accProvider.Auth

docs/resources/csm_threats_agent_rule.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ resource "datadog_csm_threats_agent_rule" "my_agent_rule" {
2929
- `enabled` (Boolean) Indicates Whether the Agent rule is enabled.
3030
- `expression` (String) The SECL expression of the Agent rule
3131
- `name` (String) The name of the Agent rule.
32-
- `policy_id` (String) The ID of the agent policy in which the rule is saved
3332

3433
### Optional
3534

3635
- `description` (String) A description for the Agent rule.
36+
- `policy_id` (String) The ID of the agent policy in which the rule is saved
3737
- `product_tags` (Set of String) The list of product tags associated with the rule
3838

3939
### Read-Only

0 commit comments

Comments
 (0)