Syncing security_monitoring_rules fails to create every non-default rule that has case conditions. There are two compounding bugs:
- Masking bug: the 400-handling code does error_obj["errors"], but the API error body uses a different shape, so it raises KeyError: 'errors'. That KeyError replaces the real Datadog message, and the log only shows a useless message.
|
except CustomClientHTTPError as err: |
|
if err.status_code == 400: |
|
preamble = "400 Bad Request - " |
|
error_json_no_preamble = err.args[0][len(preamble) :] |
|
error_obj = json.loads(error_json_no_preamble) |
|
errors = error_obj["errors"] |
|
for error_message in errors: |
|
if error_message in self.errors_to_skip: |
|
raise SkipResource(_id, self.resource_type, err.args[0]) |
|
raise err |
Example output:
2026-06-25 16:33:22,449 - ERROR - [security_monitoring_rules - xxx-xxx-xxx] - 'errors'
The body is {"error": {...}}, not the {"errors": [...]} the code assumes. Once I fixed this bug locally, I found the real bug
2026-06-25 16:59:31,955 - ERROR - [security_monitoring_rules - xxx-xxx-xxx] - 400 Bad Request - {"error":{"code":"InvalidArgument","message":"Invalid rule configuration","details":[{"code":"InvalidArgument","message":"Query a must be used at least once in a case","target":"cases"},{"code":"InvalidArgument","message":"Case condition cannot be empty","target":"cases[0].condition"}]}}
- Root cause: "cases.condition" is listed in excluded_attributes. Excluded attributes doesn't only affect diffing, but also it is stripped from the create/update payload too. Since condition is required by the rule API, every create is rejected with HTTP 400.
|
class SecurityMonitoringRules(BaseResource): |
|
"""Security Monitoring Rules inherits from BaseResource""" |
|
|
|
resource_type = "security_monitoring_rules" |
|
resource_config = ResourceConfig( |
|
base_path="/api/v2/security_monitoring/rules", |
|
excluded_attributes=[ |
|
"createdAt", |
|
"creationAuthorId", |
|
"updateAuthorId", |
|
"updatedAt", |
|
"isPartner", |
|
"isBeta", |
|
"isDeleted", |
|
"isDeprecated", |
|
"defaultTags", |
|
"version", |
|
"options.anomalyDetectionOptions", |
|
"options.impossibleTravelOptions", |
|
"cases.condition", |
|
], |
I can bypass this second bug by removing the cases.condition from the excluded attributes, which allows me to create the resources at the new account
Note: simply removing cases.condition from excluded_attributes fixes creation but may reintroduce the spurious diffs that #311 was trying to suppress (the API computes condition for some rule types, e.g. anomaly). The two concerns should be decoupled: keep condition in the payload, but ignore it in the diff via DeepDiff exclude_regex_paths (exclude_paths as used by excluded_attributes doesn't match list indices like root['cases'][0]['condition']).
Syncing security_monitoring_rules fails to create every non-default rule that has case conditions. There are two compounding bugs:
datadog-sync-cli/datadog_sync/model/security_monitoring_rules.py
Lines 114 to 123 in 8d60e76
Example output:
The body is {"error": {...}}, not the {"errors": [...]} the code assumes. Once I fixed this bug locally, I found the real bug
datadog-sync-cli/datadog_sync/model/security_monitoring_rules.py
Lines 26 to 46 in 8d60e76
I can bypass this second bug by removing the cases.condition from the excluded attributes, which allows me to create the resources at the new account
Note: simply removing cases.condition from excluded_attributes fixes creation but may reintroduce the spurious diffs that #311 was trying to suppress (the API computes condition for some rule types, e.g. anomaly). The two concerns should be decoupled: keep condition in the payload, but ignore it in the diff via DeepDiff exclude_regex_paths (exclude_paths as used by excluded_attributes doesn't match list indices like root['cases'][0]['condition']).