diff --git a/.generated-info b/.generated-info index 33aec3eb3..60e90b8e5 100644 --- a/.generated-info +++ b/.generated-info @@ -1,4 +1,4 @@ { - "spec_repo_commit": "c5cca50", - "generated": "2025-08-07 18:08:43.875" + "spec_repo_commit": "d02c8a3", + "generated": "2025-08-08 12:12:16.900" } diff --git a/.generator/schemas/v2/openapi.yaml b/.generator/schemas/v2/openapi.yaml index a0599c01e..4f155402b 100644 --- a/.generator/schemas/v2/openapi.yaml +++ b/.generator/schemas/v2/openapi.yaml @@ -36336,6 +36336,12 @@ components: SecurityMonitoringRuleUpdatePayload: description: Update an existing rule. properties: + calculatedFields: + description: Calculated fields. Only allowed for scheduled rules - in other + words, when schedulingOptions is also defined. + items: + $ref: '#/components/schemas/CalculatedField' + type: array cases: description: Cases for generating signals. items: @@ -36392,6 +36398,8 @@ components: items: $ref: '#/components/schemas/SecurityMonitoringReferenceTable' type: array + schedulingOptions: + $ref: '#/components/schemas/SecurityMonitoringSchedulingOptions' tags: description: Tags for generated signals. items: @@ -36418,6 +36426,27 @@ components: - $ref: '#/components/schemas/SecurityMonitoringStandardRulePayload' - $ref: '#/components/schemas/SecurityMonitoringSignalRulePayload' - $ref: '#/components/schemas/CloudConfigurationRulePayload' + SecurityMonitoringSchedulingOptions: + description: Options for scheduled rules. When this field is present, the rule + runs based on the schedule. When absent, it runs real-time on ingested logs. + nullable: true + properties: + rrule: + description: Schedule for the rule queries, written in RRULE syntax. See + [RFC](https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html) + for syntax reference. + example: FREQ=HOURLY;INTERVAL=1; + type: string + start: + description: Start date for the schedule, in ISO 8601 format without timezone. + example: '2025-07-14T12:00:00' + type: string + timezone: + description: Time zone of the start date, in the [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) + format. + example: America/New_York + type: string + type: object SecurityMonitoringSignal: description: Object description of a security signal. properties: @@ -37096,6 +37125,12 @@ components: SecurityMonitoringStandardRuleCreatePayload: description: Create a new rule. properties: + calculatedFields: + description: Calculated fields. Only allowed for scheduled rules - in other + words, when schedulingOptions is also defined. + items: + $ref: '#/components/schemas/CalculatedField' + type: array cases: description: Cases for generating signals. example: [] @@ -37148,6 +37183,8 @@ components: items: $ref: '#/components/schemas/SecurityMonitoringReferenceTable' type: array + schedulingOptions: + $ref: '#/components/schemas/SecurityMonitoringSchedulingOptions' tags: description: Tags for generated signals. example: @@ -37177,6 +37214,12 @@ components: SecurityMonitoringStandardRulePayload: description: The payload of a rule. properties: + calculatedFields: + description: Calculated fields. Only allowed for scheduled rules - in other + words, when schedulingOptions is also defined. + items: + $ref: '#/components/schemas/CalculatedField' + type: array cases: description: Cases for generating signals. example: [] @@ -37237,6 +37280,8 @@ components: items: $ref: '#/components/schemas/SecurityMonitoringReferenceTable' type: array + schedulingOptions: + $ref: '#/components/schemas/SecurityMonitoringSchedulingOptions' tags: description: Tags for generated signals. example: @@ -37293,6 +37338,14 @@ components: example: false readOnly: true type: boolean + index: + description: '**This field is currently unstable and might be removed in + a minor version upgrade.** + + The index to run the query on, if the `dataSource` is `logs`. Only used + for scheduled rules - in other words, when the `schedulingOptions` field + is present in the rule payload.' + type: string metric: deprecated: true description: '(Deprecated) The target field to aggregate over when using @@ -37320,6 +37373,12 @@ components: SecurityMonitoringStandardRuleResponse: description: Rule. properties: + calculatedFields: + description: Calculated fields. Only allowed for scheduled rules - in other + words, when schedulingOptions is also defined. + items: + $ref: '#/components/schemas/CalculatedField' + type: array cases: description: Cases for generating signals. items: @@ -37405,6 +37464,8 @@ components: items: $ref: '#/components/schemas/SecurityMonitoringReferenceTable' type: array + schedulingOptions: + $ref: '#/components/schemas/SecurityMonitoringSchedulingOptions' tags: description: Tags for generated signals. items: @@ -37436,6 +37497,12 @@ components: SecurityMonitoringStandardRuleTestPayload: description: The payload of a rule to test properties: + calculatedFields: + description: Calculated fields. Only allowed for scheduled rules - in other + words, when schedulingOptions is also defined. + items: + $ref: '#/components/schemas/CalculatedField' + type: array cases: description: Cases for generating signals. example: [] @@ -37488,6 +37555,8 @@ components: items: $ref: '#/components/schemas/SecurityMonitoringReferenceTable' type: array + schedulingOptions: + $ref: '#/components/schemas/SecurityMonitoringSchedulingOptions' tags: description: Tags for generated signals. example: diff --git a/examples/v2_security-monitoring_CreateSecurityMonitoringRule_868881438.rs b/examples/v2_security-monitoring_CreateSecurityMonitoringRule_868881438.rs new file mode 100644 index 000000000..9909fb4a1 --- /dev/null +++ b/examples/v2_security-monitoring_CreateSecurityMonitoringRule_868881438.rs @@ -0,0 +1,60 @@ +// Create a scheduled detection rule returns "OK" response +use datadog_api_client::datadog; +use datadog_api_client::datadogV2::api_security_monitoring::SecurityMonitoringAPI; +use datadog_api_client::datadogV2::model::SecurityMonitoringRuleCaseCreate; +use datadog_api_client::datadogV2::model::SecurityMonitoringRuleCreatePayload; +use datadog_api_client::datadogV2::model::SecurityMonitoringRuleEvaluationWindow; +use datadog_api_client::datadogV2::model::SecurityMonitoringRuleKeepAlive; +use datadog_api_client::datadogV2::model::SecurityMonitoringRuleMaxSignalDuration; +use datadog_api_client::datadogV2::model::SecurityMonitoringRuleOptions; +use datadog_api_client::datadogV2::model::SecurityMonitoringRuleQueryAggregation; +use datadog_api_client::datadogV2::model::SecurityMonitoringRuleSeverity; +use datadog_api_client::datadogV2::model::SecurityMonitoringRuleTypeCreate; +use datadog_api_client::datadogV2::model::SecurityMonitoringSchedulingOptions; +use datadog_api_client::datadogV2::model::SecurityMonitoringStandardRuleCreatePayload; +use datadog_api_client::datadogV2::model::SecurityMonitoringStandardRuleQuery; + +#[tokio::main] +async fn main() { + let body = + SecurityMonitoringRuleCreatePayload::SecurityMonitoringStandardRuleCreatePayload(Box::new( + SecurityMonitoringStandardRuleCreatePayload::new( + vec![ + SecurityMonitoringRuleCaseCreate::new(SecurityMonitoringRuleSeverity::INFO) + .condition("a > 0".to_string()) + .name("".to_string()) + .notifications(vec![]), + ], + true, + "Test rule".to_string(), + "Example-Security-Monitoring".to_string(), + SecurityMonitoringRuleOptions::new() + .evaluation_window(SecurityMonitoringRuleEvaluationWindow::FIFTEEN_MINUTES) + .keep_alive(SecurityMonitoringRuleKeepAlive::ONE_HOUR) + .max_signal_duration(SecurityMonitoringRuleMaxSignalDuration::ONE_DAY), + vec![SecurityMonitoringStandardRuleQuery::new() + .aggregation(SecurityMonitoringRuleQueryAggregation::COUNT) + .distinct_fields(vec![]) + .group_by_fields(vec![]) + .index("main".to_string()) + .query("@test:true".to_string())], + ) + .filters(vec![]) + .scheduling_options(Some( + SecurityMonitoringSchedulingOptions::new() + .rrule("FREQ=HOURLY;INTERVAL=2;".to_string()) + .start("2025-06-18T12:00:00".to_string()) + .timezone("Europe/Paris".to_string()), + )) + .tags(vec![]) + .type_(SecurityMonitoringRuleTypeCreate::LOG_DETECTION), + )); + let configuration = datadog::Configuration::new(); + let api = SecurityMonitoringAPI::with_config(configuration); + let resp = api.create_security_monitoring_rule(body).await; + if let Ok(value) = resp { + println!("{:#?}", value); + } else { + println!("{:#?}", resp.unwrap_err()); + } +} diff --git a/src/datadogV2/model/mod.rs b/src/datadogV2/model/mod.rs index e984027ea..81ad073ed 100644 --- a/src/datadogV2/model/mod.rs +++ b/src/datadogV2/model/mod.rs @@ -4656,6 +4656,8 @@ pub mod model_security_monitoring_list_rules_response; pub use self::model_security_monitoring_list_rules_response::SecurityMonitoringListRulesResponse; pub mod model_security_monitoring_standard_rule_response; pub use self::model_security_monitoring_standard_rule_response::SecurityMonitoringStandardRuleResponse; +pub mod model_calculated_field; +pub use self::model_calculated_field::CalculatedField; pub mod model_security_monitoring_rule_case; pub use self::model_security_monitoring_rule_case::SecurityMonitoringRuleCase; pub mod model_security_monitoring_rule_case_action; @@ -4714,6 +4716,8 @@ pub mod model_security_monitoring_standard_data_source; pub use self::model_security_monitoring_standard_data_source::SecurityMonitoringStandardDataSource; pub mod model_security_monitoring_reference_table; pub use self::model_security_monitoring_reference_table::SecurityMonitoringReferenceTable; +pub mod model_security_monitoring_scheduling_options; +pub use self::model_security_monitoring_scheduling_options::SecurityMonitoringSchedulingOptions; pub mod model_security_monitoring_third_party_rule_case; pub use self::model_security_monitoring_third_party_rule_case::SecurityMonitoringThirdPartyRuleCase; pub mod model_security_monitoring_rule_type_read; @@ -5138,8 +5142,6 @@ pub mod model_historical_job_response_attributes; pub use self::model_historical_job_response_attributes::HistoricalJobResponseAttributes; pub mod model_job_definition; pub use self::model_job_definition::JobDefinition; -pub mod model_calculated_field; -pub use self::model_calculated_field::CalculatedField; pub mod model_historical_job_options; pub use self::model_historical_job_options::HistoricalJobOptions; pub mod model_historical_job_query; diff --git a/src/datadogV2/model/model_security_monitoring_rule_update_payload.rs b/src/datadogV2/model/model_security_monitoring_rule_update_payload.rs index 375fa745e..a4fb274a0 100644 --- a/src/datadogV2/model/model_security_monitoring_rule_update_payload.rs +++ b/src/datadogV2/model/model_security_monitoring_rule_update_payload.rs @@ -11,6 +11,9 @@ use std::fmt::{self, Formatter}; #[skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize)] pub struct SecurityMonitoringRuleUpdatePayload { + /// Calculated fields. Only allowed for scheduled rules - in other words, when schedulingOptions is also defined. + #[serde(rename = "calculatedFields")] + pub calculated_fields: Option>, /// Cases for generating signals. #[serde(rename = "cases")] pub cases: Option>, @@ -51,6 +54,14 @@ pub struct SecurityMonitoringRuleUpdatePayload { /// Reference tables for the rule. #[serde(rename = "referenceTables")] pub reference_tables: Option>, + /// Options for scheduled rules. When this field is present, the rule runs based on the schedule. When absent, it runs real-time on ingested logs. + #[serde( + rename = "schedulingOptions", + default, + with = "::serde_with::rust::double_option" + )] + pub scheduling_options: + Option>, /// Tags for generated signals. #[serde(rename = "tags")] pub tags: Option>, @@ -71,6 +82,7 @@ pub struct SecurityMonitoringRuleUpdatePayload { impl SecurityMonitoringRuleUpdatePayload { pub fn new() -> SecurityMonitoringRuleUpdatePayload { SecurityMonitoringRuleUpdatePayload { + calculated_fields: None, cases: None, compliance_signal_options: None, custom_message: None, @@ -84,6 +96,7 @@ impl SecurityMonitoringRuleUpdatePayload { options: None, queries: None, reference_tables: None, + scheduling_options: None, tags: None, third_party_cases: None, version: None, @@ -92,6 +105,14 @@ impl SecurityMonitoringRuleUpdatePayload { } } + pub fn calculated_fields( + mut self, + value: Vec, + ) -> Self { + self.calculated_fields = Some(value); + self + } + pub fn cases( mut self, value: Vec, @@ -175,6 +196,14 @@ impl SecurityMonitoringRuleUpdatePayload { self } + pub fn scheduling_options( + mut self, + value: Option, + ) -> Self { + self.scheduling_options = Some(value); + self + } + pub fn tags(mut self, value: Vec) -> Self { self.tags = Some(value); self @@ -225,6 +254,8 @@ impl<'de> Deserialize<'de> for SecurityMonitoringRuleUpdatePayload { where M: MapAccess<'a>, { + let mut calculated_fields: Option> = + None; let mut cases: Option> = None; let mut compliance_signal_options: Option< @@ -246,6 +277,9 @@ impl<'de> Deserialize<'de> for SecurityMonitoringRuleUpdatePayload { let mut reference_tables: Option< Vec, > = None; + let mut scheduling_options: Option< + Option, + > = None; let mut tags: Option> = None; let mut third_party_cases: Option< Vec, @@ -259,6 +293,13 @@ impl<'de> Deserialize<'de> for SecurityMonitoringRuleUpdatePayload { while let Some((k, v)) = map.next_entry::()? { match k.as_str() { + "calculatedFields" => { + if v.is_null() { + continue; + } + calculated_fields = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "cases" => { if v.is_null() { continue; @@ -343,6 +384,10 @@ impl<'de> Deserialize<'de> for SecurityMonitoringRuleUpdatePayload { reference_tables = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } + "schedulingOptions" => { + scheduling_options = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "tags" => { if v.is_null() { continue; @@ -371,6 +416,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringRuleUpdatePayload { } let content = SecurityMonitoringRuleUpdatePayload { + calculated_fields, cases, compliance_signal_options, custom_message, @@ -384,6 +430,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringRuleUpdatePayload { options, queries, reference_tables, + scheduling_options, tags, third_party_cases, version, diff --git a/src/datadogV2/model/model_security_monitoring_scheduling_options.rs b/src/datadogV2/model/model_security_monitoring_scheduling_options.rs new file mode 100644 index 000000000..03f3799aa --- /dev/null +++ b/src/datadogV2/model/model_security_monitoring_scheduling_options.rs @@ -0,0 +1,139 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2019-Present Datadog, Inc. +use serde::de::{Error, MapAccess, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::skip_serializing_none; +use std::fmt::{self, Formatter}; + +/// Options for scheduled rules. When this field is present, the rule runs based on the schedule. When absent, it runs real-time on ingested logs. +#[non_exhaustive] +#[skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct SecurityMonitoringSchedulingOptions { + /// Schedule for the rule queries, written in RRULE syntax. See [RFC]() for syntax reference. + #[serde(rename = "rrule")] + pub rrule: Option, + /// Start date for the schedule, in ISO 8601 format without timezone. + #[serde(rename = "start")] + pub start: Option, + /// Time zone of the start date, in the [tz database]() format. + #[serde(rename = "timezone")] + pub timezone: Option, + #[serde(flatten)] + pub additional_properties: std::collections::BTreeMap, + #[serde(skip)] + #[serde(default)] + pub(crate) _unparsed: bool, +} + +impl SecurityMonitoringSchedulingOptions { + pub fn new() -> SecurityMonitoringSchedulingOptions { + SecurityMonitoringSchedulingOptions { + rrule: None, + start: None, + timezone: None, + additional_properties: std::collections::BTreeMap::new(), + _unparsed: false, + } + } + + pub fn rrule(mut self, value: String) -> Self { + self.rrule = Some(value); + self + } + + pub fn start(mut self, value: String) -> Self { + self.start = Some(value); + self + } + + pub fn timezone(mut self, value: String) -> Self { + self.timezone = Some(value); + self + } + + pub fn additional_properties( + mut self, + value: std::collections::BTreeMap, + ) -> Self { + self.additional_properties = value; + self + } +} + +impl Default for SecurityMonitoringSchedulingOptions { + fn default() -> Self { + Self::new() + } +} + +impl<'de> Deserialize<'de> for SecurityMonitoringSchedulingOptions { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct SecurityMonitoringSchedulingOptionsVisitor; + impl<'a> Visitor<'a> for SecurityMonitoringSchedulingOptionsVisitor { + type Value = SecurityMonitoringSchedulingOptions; + + fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str("a mapping") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'a>, + { + let mut rrule: Option = None; + let mut start: Option = None; + let mut timezone: Option = None; + let mut additional_properties: std::collections::BTreeMap< + String, + serde_json::Value, + > = std::collections::BTreeMap::new(); + let mut _unparsed = false; + + while let Some((k, v)) = map.next_entry::()? { + match k.as_str() { + "rrule" => { + if v.is_null() { + continue; + } + rrule = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "start" => { + if v.is_null() { + continue; + } + start = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + "timezone" => { + if v.is_null() { + continue; + } + timezone = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } + &_ => { + if let Ok(value) = serde_json::from_value(v.clone()) { + additional_properties.insert(k, value); + } + } + } + } + + let content = SecurityMonitoringSchedulingOptions { + rrule, + start, + timezone, + additional_properties, + _unparsed, + }; + + Ok(content) + } + } + + deserializer.deserialize_any(SecurityMonitoringSchedulingOptionsVisitor) + } +} diff --git a/src/datadogV2/model/model_security_monitoring_standard_rule_create_payload.rs b/src/datadogV2/model/model_security_monitoring_standard_rule_create_payload.rs index 9af0f1591..ac8935c40 100644 --- a/src/datadogV2/model/model_security_monitoring_standard_rule_create_payload.rs +++ b/src/datadogV2/model/model_security_monitoring_standard_rule_create_payload.rs @@ -11,6 +11,9 @@ use std::fmt::{self, Formatter}; #[skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize)] pub struct SecurityMonitoringStandardRuleCreatePayload { + /// Calculated fields. Only allowed for scheduled rules - in other words, when schedulingOptions is also defined. + #[serde(rename = "calculatedFields")] + pub calculated_fields: Option>, /// Cases for generating signals. #[serde(rename = "cases")] pub cases: Vec, @@ -41,6 +44,14 @@ pub struct SecurityMonitoringStandardRuleCreatePayload { /// Reference tables for the rule. #[serde(rename = "referenceTables")] pub reference_tables: Option>, + /// Options for scheduled rules. When this field is present, the rule runs based on the schedule. When absent, it runs real-time on ingested logs. + #[serde( + rename = "schedulingOptions", + default, + with = "::serde_with::rust::double_option" + )] + pub scheduling_options: + Option>, /// Tags for generated signals. #[serde(rename = "tags")] pub tags: Option>, @@ -68,6 +79,7 @@ impl SecurityMonitoringStandardRuleCreatePayload { queries: Vec, ) -> SecurityMonitoringStandardRuleCreatePayload { SecurityMonitoringStandardRuleCreatePayload { + calculated_fields: None, cases, filters: None, group_signals_by: None, @@ -78,6 +90,7 @@ impl SecurityMonitoringStandardRuleCreatePayload { options, queries, reference_tables: None, + scheduling_options: None, tags: None, third_party_cases: None, type_: None, @@ -86,6 +99,14 @@ impl SecurityMonitoringStandardRuleCreatePayload { } } + pub fn calculated_fields( + mut self, + value: Vec, + ) -> Self { + self.calculated_fields = Some(value); + self + } + pub fn filters( mut self, value: Vec, @@ -112,6 +133,14 @@ impl SecurityMonitoringStandardRuleCreatePayload { self } + pub fn scheduling_options( + mut self, + value: Option, + ) -> Self { + self.scheduling_options = Some(value); + self + } + pub fn tags(mut self, value: Vec) -> Self { self.tags = Some(value); self @@ -159,6 +188,8 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleCreatePayload { where M: MapAccess<'a>, { + let mut calculated_fields: Option> = + None; let mut cases: Option< Vec, > = None; @@ -177,6 +208,9 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleCreatePayload { let mut reference_tables: Option< Vec, > = None; + let mut scheduling_options: Option< + Option, + > = None; let mut tags: Option> = None; let mut third_party_cases: Option< Vec, @@ -191,6 +225,13 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleCreatePayload { while let Some((k, v)) = map.next_entry::()? { match k.as_str() { + "calculatedFields" => { + if v.is_null() { + continue; + } + calculated_fields = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "cases" => { cases = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } @@ -236,6 +277,10 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleCreatePayload { reference_tables = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } + "schedulingOptions" => { + scheduling_options = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "tags" => { if v.is_null() { continue; @@ -278,6 +323,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleCreatePayload { let queries = queries.ok_or_else(|| M::Error::missing_field("queries"))?; let content = SecurityMonitoringStandardRuleCreatePayload { + calculated_fields, cases, filters, group_signals_by, @@ -288,6 +334,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleCreatePayload { options, queries, reference_tables, + scheduling_options, tags, third_party_cases, type_, diff --git a/src/datadogV2/model/model_security_monitoring_standard_rule_payload.rs b/src/datadogV2/model/model_security_monitoring_standard_rule_payload.rs index e0a8eb8e9..899571036 100644 --- a/src/datadogV2/model/model_security_monitoring_standard_rule_payload.rs +++ b/src/datadogV2/model/model_security_monitoring_standard_rule_payload.rs @@ -11,6 +11,9 @@ use std::fmt::{self, Formatter}; #[skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize)] pub struct SecurityMonitoringStandardRulePayload { + /// Calculated fields. Only allowed for scheduled rules - in other words, when schedulingOptions is also defined. + #[serde(rename = "calculatedFields")] + pub calculated_fields: Option>, /// Cases for generating signals. #[serde(rename = "cases")] pub cases: Vec, @@ -47,6 +50,14 @@ pub struct SecurityMonitoringStandardRulePayload { /// Reference tables for the rule. #[serde(rename = "referenceTables")] pub reference_tables: Option>, + /// Options for scheduled rules. When this field is present, the rule runs based on the schedule. When absent, it runs real-time on ingested logs. + #[serde( + rename = "schedulingOptions", + default, + with = "::serde_with::rust::double_option" + )] + pub scheduling_options: + Option>, /// Tags for generated signals. #[serde(rename = "tags")] pub tags: Option>, @@ -74,6 +85,7 @@ impl SecurityMonitoringStandardRulePayload { queries: Vec, ) -> SecurityMonitoringStandardRulePayload { SecurityMonitoringStandardRulePayload { + calculated_fields: None, cases, custom_message: None, custom_name: None, @@ -86,6 +98,7 @@ impl SecurityMonitoringStandardRulePayload { options, queries, reference_tables: None, + scheduling_options: None, tags: None, third_party_cases: None, type_: None, @@ -94,6 +107,14 @@ impl SecurityMonitoringStandardRulePayload { } } + pub fn calculated_fields( + mut self, + value: Vec, + ) -> Self { + self.calculated_fields = Some(value); + self + } + pub fn custom_message(mut self, value: String) -> Self { self.custom_message = Some(value); self @@ -130,6 +151,14 @@ impl SecurityMonitoringStandardRulePayload { self } + pub fn scheduling_options( + mut self, + value: Option, + ) -> Self { + self.scheduling_options = Some(value); + self + } + pub fn tags(mut self, value: Vec) -> Self { self.tags = Some(value); self @@ -177,6 +206,8 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRulePayload { where M: MapAccess<'a>, { + let mut calculated_fields: Option> = + None; let mut cases: Option< Vec, > = None; @@ -197,6 +228,9 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRulePayload { let mut reference_tables: Option< Vec, > = None; + let mut scheduling_options: Option< + Option, + > = None; let mut tags: Option> = None; let mut third_party_cases: Option< Vec, @@ -211,6 +245,13 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRulePayload { while let Some((k, v)) = map.next_entry::()? { match k.as_str() { + "calculatedFields" => { + if v.is_null() { + continue; + } + calculated_fields = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "cases" => { cases = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } @@ -270,6 +311,10 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRulePayload { reference_tables = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } + "schedulingOptions" => { + scheduling_options = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "tags" => { if v.is_null() { continue; @@ -312,6 +357,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRulePayload { let queries = queries.ok_or_else(|| M::Error::missing_field("queries"))?; let content = SecurityMonitoringStandardRulePayload { + calculated_fields, cases, custom_message, custom_name, @@ -324,6 +370,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRulePayload { options, queries, reference_tables, + scheduling_options, tags, third_party_cases, type_, diff --git a/src/datadogV2/model/model_security_monitoring_standard_rule_query.rs b/src/datadogV2/model/model_security_monitoring_standard_rule_query.rs index db267004f..4b555361f 100644 --- a/src/datadogV2/model/model_security_monitoring_standard_rule_query.rs +++ b/src/datadogV2/model/model_security_monitoring_standard_rule_query.rs @@ -29,6 +29,10 @@ pub struct SecurityMonitoringStandardRuleQuery { /// When false, events without a group-by value are ignored by the rule. When true, events with missing group-by fields are processed with `N/A`, replacing the missing values. #[serde(rename = "hasOptionalGroupByFields")] pub has_optional_group_by_fields: Option, + /// **This field is currently unstable and might be removed in a minor version upgrade.** + /// The index to run the query on, if the `dataSource` is `logs`. Only used for scheduled rules - in other words, when the `schedulingOptions` field is present in the rule payload. + #[serde(rename = "index")] + pub index: Option, /// (Deprecated) The target field to aggregate over when using the sum or max /// aggregations. `metrics` field should be used instead. #[deprecated] @@ -60,6 +64,7 @@ impl SecurityMonitoringStandardRuleQuery { distinct_fields: None, group_by_fields: None, has_optional_group_by_fields: None, + index: None, metric: None, metrics: None, name: None, @@ -111,6 +116,12 @@ impl SecurityMonitoringStandardRuleQuery { self } + #[allow(deprecated)] + pub fn index(mut self, value: String) -> Self { + self.index = Some(value); + self + } + #[allow(deprecated)] pub fn metric(mut self, value: String) -> Self { self.metric = Some(value); @@ -177,6 +188,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleQuery { let mut distinct_fields: Option> = None; let mut group_by_fields: Option> = None; let mut has_optional_group_by_fields: Option = None; + let mut index: Option = None; let mut metric: Option = None; let mut metrics: Option> = None; let mut name: Option = None; @@ -247,6 +259,12 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleQuery { has_optional_group_by_fields = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } + "index" => { + if v.is_null() { + continue; + } + index = Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "metric" => { if v.is_null() { continue; @@ -287,6 +305,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleQuery { distinct_fields, group_by_fields, has_optional_group_by_fields, + index, metric, metrics, name, diff --git a/src/datadogV2/model/model_security_monitoring_standard_rule_response.rs b/src/datadogV2/model/model_security_monitoring_standard_rule_response.rs index 0721ccdfb..6c0ec8794 100644 --- a/src/datadogV2/model/model_security_monitoring_standard_rule_response.rs +++ b/src/datadogV2/model/model_security_monitoring_standard_rule_response.rs @@ -11,6 +11,9 @@ use std::fmt::{self, Formatter}; #[skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize)] pub struct SecurityMonitoringStandardRuleResponse { + /// Calculated fields. Only allowed for scheduled rules - in other words, when schedulingOptions is also defined. + #[serde(rename = "calculatedFields")] + pub calculated_fields: Option>, /// Cases for generating signals. #[serde(rename = "cases")] pub cases: Option>, @@ -72,6 +75,14 @@ pub struct SecurityMonitoringStandardRuleResponse { /// Reference tables for the rule. #[serde(rename = "referenceTables")] pub reference_tables: Option>, + /// Options for scheduled rules. When this field is present, the rule runs based on the schedule. When absent, it runs real-time on ingested logs. + #[serde( + rename = "schedulingOptions", + default, + with = "::serde_with::rust::double_option" + )] + pub scheduling_options: + Option>, /// Tags for generated signals. #[serde(rename = "tags")] pub tags: Option>, @@ -101,6 +112,7 @@ pub struct SecurityMonitoringStandardRuleResponse { impl SecurityMonitoringStandardRuleResponse { pub fn new() -> SecurityMonitoringStandardRuleResponse { SecurityMonitoringStandardRuleResponse { + calculated_fields: None, cases: None, compliance_signal_options: None, created_at: None, @@ -121,6 +133,7 @@ impl SecurityMonitoringStandardRuleResponse { options: None, queries: None, reference_tables: None, + scheduling_options: None, tags: None, third_party_cases: None, type_: None, @@ -132,6 +145,14 @@ impl SecurityMonitoringStandardRuleResponse { } } + pub fn calculated_fields( + mut self, + value: Vec, + ) -> Self { + self.calculated_fields = Some(value); + self + } + pub fn cases( mut self, value: Vec, @@ -250,6 +271,14 @@ impl SecurityMonitoringStandardRuleResponse { self } + pub fn scheduling_options( + mut self, + value: Option, + ) -> Self { + self.scheduling_options = Some(value); + self + } + pub fn tags(mut self, value: Vec) -> Self { self.tags = Some(value); self @@ -315,6 +344,8 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleResponse { where M: MapAccess<'a>, { + let mut calculated_fields: Option> = + None; let mut cases: Option> = None; let mut compliance_signal_options: Option< @@ -344,6 +375,9 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleResponse { let mut reference_tables: Option< Vec, > = None; + let mut scheduling_options: Option< + Option, + > = None; let mut tags: Option> = None; let mut third_party_cases: Option< Vec, @@ -361,6 +395,13 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleResponse { while let Some((k, v)) = map.next_entry::()? { match k.as_str() { + "calculatedFields" => { + if v.is_null() { + continue; + } + calculated_fields = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "cases" => { if v.is_null() { continue; @@ -490,6 +531,10 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleResponse { reference_tables = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } + "schedulingOptions" => { + scheduling_options = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "tags" => { if v.is_null() { continue; @@ -545,6 +590,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleResponse { } let content = SecurityMonitoringStandardRuleResponse { + calculated_fields, cases, compliance_signal_options, created_at, @@ -565,6 +611,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleResponse { options, queries, reference_tables, + scheduling_options, tags, third_party_cases, type_, diff --git a/src/datadogV2/model/model_security_monitoring_standard_rule_test_payload.rs b/src/datadogV2/model/model_security_monitoring_standard_rule_test_payload.rs index 7c76392e4..a75686572 100644 --- a/src/datadogV2/model/model_security_monitoring_standard_rule_test_payload.rs +++ b/src/datadogV2/model/model_security_monitoring_standard_rule_test_payload.rs @@ -11,6 +11,9 @@ use std::fmt::{self, Formatter}; #[skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize)] pub struct SecurityMonitoringStandardRuleTestPayload { + /// Calculated fields. Only allowed for scheduled rules - in other words, when schedulingOptions is also defined. + #[serde(rename = "calculatedFields")] + pub calculated_fields: Option>, /// Cases for generating signals. #[serde(rename = "cases")] pub cases: Vec, @@ -41,6 +44,14 @@ pub struct SecurityMonitoringStandardRuleTestPayload { /// Reference tables for the rule. #[serde(rename = "referenceTables")] pub reference_tables: Option>, + /// Options for scheduled rules. When this field is present, the rule runs based on the schedule. When absent, it runs real-time on ingested logs. + #[serde( + rename = "schedulingOptions", + default, + with = "::serde_with::rust::double_option" + )] + pub scheduling_options: + Option>, /// Tags for generated signals. #[serde(rename = "tags")] pub tags: Option>, @@ -68,6 +79,7 @@ impl SecurityMonitoringStandardRuleTestPayload { queries: Vec, ) -> SecurityMonitoringStandardRuleTestPayload { SecurityMonitoringStandardRuleTestPayload { + calculated_fields: None, cases, filters: None, group_signals_by: None, @@ -78,6 +90,7 @@ impl SecurityMonitoringStandardRuleTestPayload { options, queries, reference_tables: None, + scheduling_options: None, tags: None, third_party_cases: None, type_: None, @@ -86,6 +99,14 @@ impl SecurityMonitoringStandardRuleTestPayload { } } + pub fn calculated_fields( + mut self, + value: Vec, + ) -> Self { + self.calculated_fields = Some(value); + self + } + pub fn filters( mut self, value: Vec, @@ -112,6 +133,14 @@ impl SecurityMonitoringStandardRuleTestPayload { self } + pub fn scheduling_options( + mut self, + value: Option, + ) -> Self { + self.scheduling_options = Some(value); + self + } + pub fn tags(mut self, value: Vec) -> Self { self.tags = Some(value); self @@ -156,6 +185,8 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleTestPayload { where M: MapAccess<'a>, { + let mut calculated_fields: Option> = + None; let mut cases: Option< Vec, > = None; @@ -174,6 +205,9 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleTestPayload { let mut reference_tables: Option< Vec, > = None; + let mut scheduling_options: Option< + Option, + > = None; let mut tags: Option> = None; let mut third_party_cases: Option< Vec, @@ -188,6 +222,13 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleTestPayload { while let Some((k, v)) = map.next_entry::()? { match k.as_str() { + "calculatedFields" => { + if v.is_null() { + continue; + } + calculated_fields = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "cases" => { cases = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } @@ -233,6 +274,10 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleTestPayload { reference_tables = Some(serde_json::from_value(v).map_err(M::Error::custom)?); } + "schedulingOptions" => { + scheduling_options = + Some(serde_json::from_value(v).map_err(M::Error::custom)?); + } "tags" => { if v.is_null() { continue; @@ -275,6 +320,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleTestPayload { let queries = queries.ok_or_else(|| M::Error::missing_field("queries"))?; let content = SecurityMonitoringStandardRuleTestPayload { + calculated_fields, cases, filters, group_signals_by, @@ -285,6 +331,7 @@ impl<'de> Deserialize<'de> for SecurityMonitoringStandardRuleTestPayload { options, queries, reference_tables, + scheduling_options, tags, third_party_cases, type_, diff --git a/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-detection-rule-returns-OK-response.frozen b/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-detection-rule-returns-OK-response.frozen new file mode 100644 index 000000000..5c8f2a4f0 --- /dev/null +++ b/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-detection-rule-returns-OK-response.frozen @@ -0,0 +1 @@ +2025-07-31T07:48:27.113Z \ No newline at end of file diff --git a/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-detection-rule-returns-OK-response.json b/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-detection-rule-returns-OK-response.json new file mode 100644 index 000000000..7d4cde86b --- /dev/null +++ b/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-detection-rule-returns-OK-response.json @@ -0,0 +1,63 @@ +{ + "http_interactions": [ + { + "request": { + "body": { + "string": "{\"cases\":[{\"condition\":\"a > 0\",\"name\":\"\",\"notifications\":[],\"status\":\"info\"}],\"filters\":[],\"isEnabled\":true,\"message\":\"Test rule\",\"name\":\"Test-Create_a_scheduled_detection_rule_returns_OK_response-1753948107\",\"options\":{\"evaluationWindow\":900,\"keepAlive\":3600,\"maxSignalDuration\":86400},\"queries\":[{\"aggregation\":\"count\",\"distinctFields\":[],\"groupByFields\":[],\"index\":\"main\",\"query\":\"@test:true\"}],\"schedulingOptions\":{\"rrule\":\"FREQ=HOURLY;INTERVAL=2;\",\"start\":\"2025-06-18T12:00:00\",\"timezone\":\"Europe/Paris\"},\"tags\":[],\"type\":\"log_detection\"}", + "encoding": null + }, + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json" + ] + }, + "method": "post", + "uri": "https://api.datadoghq.com/api/v2/security_monitoring/rules" + }, + "response": { + "body": { + "string": "{\"name\":\"Test-Create_a_scheduled_detection_rule_returns_OK_response-1753948107\",\"createdAt\":1753948107557,\"isDefault\":false,\"isPartner\":false,\"isEnabled\":true,\"isBeta\":false,\"isDeleted\":false,\"isDeprecated\":false,\"queries\":[{\"query\":\"@test:true\",\"groupByFields\":[],\"hasOptionalGroupByFields\":false,\"distinctFields\":[],\"aggregation\":\"count\",\"name\":\"\",\"dataSource\":\"logs\",\"index\":\"main\"}],\"options\":{\"evaluationWindow\":900,\"detectionMethod\":\"threshold\",\"maxSignalDuration\":86400,\"keepAlive\":3600},\"cases\":[{\"name\":\"\",\"status\":\"info\",\"notifications\":[],\"condition\":\"a \\u003e 0\"}],\"message\":\"Test rule\",\"tags\":[],\"hasExtendedTitle\":false,\"type\":\"log_detection\",\"filters\":[],\"version\":1,\"id\":\"8dd-els-oyn\",\"blocking\":false,\"metadata\":{\"entities\":null,\"sources\":null},\"creationAuthorId\":1445416,\"creator\":{\"handle\":\"frog@datadoghq.com\",\"name\":\"frog\"},\"updater\":{\"handle\":\"\",\"name\":\"\"},\"schedulingOptions\":{\"rrule\":\"FREQ=HOURLY;INTERVAL=2;\",\"start\":\"2025-06-18T12:00:00\",\"timezone\":\"Europe/Paris\"}}", + "encoding": null + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "status": { + "code": 200, + "message": "OK" + } + }, + "recorded_at": "Thu, 31 Jul 2025 07:48:27 GMT" + }, + { + "request": { + "body": "", + "headers": { + "Accept": [ + "*/*" + ] + }, + "method": "delete", + "uri": "https://api.datadoghq.com/api/v2/security_monitoring/rules/8dd-els-oyn" + }, + "response": { + "body": { + "string": "", + "encoding": null + }, + "headers": {}, + "status": { + "code": 204, + "message": "No Content" + } + }, + "recorded_at": "Thu, 31 Jul 2025 07:48:27 GMT" + } + ], + "recorded_with": "VCR 6.0.0" +} \ No newline at end of file diff --git a/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-rule-without-rrule-returns-Bad-Request-response.frozen b/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-rule-without-rrule-returns-Bad-Request-response.frozen new file mode 100644 index 000000000..74170d6ac --- /dev/null +++ b/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-rule-without-rrule-returns-Bad-Request-response.frozen @@ -0,0 +1 @@ +2025-07-31T07:49:14.474Z \ No newline at end of file diff --git a/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-rule-without-rrule-returns-Bad-Request-response.json b/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-rule-without-rrule-returns-Bad-Request-response.json new file mode 100644 index 000000000..5fb7a9624 --- /dev/null +++ b/tests/scenarios/cassettes/v2/security_monitoring/Create-a-scheduled-rule-without-rrule-returns-Bad-Request-response.json @@ -0,0 +1,39 @@ +{ + "http_interactions": [ + { + "request": { + "body": { + "string": "{\"cases\":[{\"condition\":\"a > 0\",\"name\":\"\",\"notifications\":[],\"status\":\"info\"}],\"filters\":[],\"isEnabled\":true,\"message\":\"Test rule\",\"name\":\"Test-Create_a_scheduled_rule_without_rrule_returns_Bad_Request_response-1753948154\",\"options\":{\"evaluationWindow\":900,\"keepAlive\":3600,\"maxSignalDuration\":86400},\"queries\":[{\"aggregation\":\"count\",\"distinctFields\":[],\"groupByFields\":[],\"index\":\"main\",\"query\":\"@test:true\"}],\"schedulingOptions\":{\"start\":\"2025-06-18T12:00:00\",\"timezone\":\"Europe/Paris\"},\"tags\":[],\"type\":\"log_detection\"}", + "encoding": null + }, + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json" + ] + }, + "method": "post", + "uri": "https://api.datadoghq.com/api/v2/security_monitoring/rules" + }, + "response": { + "body": { + "string": "{\"error\":{\"code\":\"InvalidArgument\",\"message\":\"Invalid rule configuration\",\"details\":[{\"code\":\"InvalidArgument\",\"message\":\"The RRULE schedule is invalid for scheduled rules\",\"target\":\"schedulingOptions.rrule\"}]}}", + "encoding": null + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "status": { + "code": 400, + "message": "Bad Request" + } + }, + "recorded_at": "Thu, 31 Jul 2025 07:49:14 GMT" + } + ], + "recorded_with": "VCR 6.0.0" +} \ No newline at end of file diff --git a/tests/scenarios/features/v2/security_monitoring.feature b/tests/scenarios/features/v2/security_monitoring.feature index 28df5a064..3acea8148 100644 --- a/tests/scenarios/features/v2/security_monitoring.feature +++ b/tests/scenarios/features/v2/security_monitoring.feature @@ -295,6 +295,24 @@ Feature: Security Monitoring When the request is sent Then the response status is 201 Successfully created the notification rule. + @team:DataDog/k9-cloud-security-platform + Scenario: Create a scheduled detection rule returns "OK" response + Given new "CreateSecurityMonitoringRule" request + And body with value {"name":"{{ unique }}", "queries":[{"query":"@test:true","aggregation":"count","groupByFields":[],"distinctFields":[],"index":"main"}],"filters":[],"cases":[{"name":"","status":"info","condition":"a > 0","notifications":[]}],"options":{"evaluationWindow":900,"keepAlive":3600,"maxSignalDuration":86400},"message":"Test rule","tags":[],"isEnabled":true, "type":"log_detection", "schedulingOptions": {"rrule": "FREQ=HOURLY;INTERVAL=2;", "start": "2025-06-18T12:00:00", "timezone": "Europe/Paris"}} + When the request is sent + Then the response status is 200 OK + And the response "name" is equal to "{{ unique }}" + And the response "type" is equal to "log_detection" + And the response "message" is equal to "Test rule" + And the response "schedulingOptions" is equal to {"rrule": "FREQ=HOURLY;INTERVAL=2;", "start": "2025-06-18T12:00:00", "timezone": "Europe/Paris"} + + @team:DataDog/k9-cloud-security-platform + Scenario: Create a scheduled rule without rrule returns "Bad Request" response + Given new "CreateSecurityMonitoringRule" request + And body with value {"name":"{{ unique }}", "queries":[{"query":"@test:true","aggregation":"count","groupByFields":[],"distinctFields":[],"index":"main"}],"filters":[],"cases":[{"name":"","status":"info","condition":"a > 0","notifications":[]}],"options":{"evaluationWindow":900,"keepAlive":3600,"maxSignalDuration":86400},"message":"Test rule","tags":[],"isEnabled":true, "type":"log_detection", "schedulingOptions": {"start": "2025-06-18T12:00:00", "timezone": "Europe/Paris"}} + When the request is sent + Then the response status is 400 Bad Request + @generated @skip @team:DataDog/k9-cloud-security-platform Scenario: Create a security filter returns "Bad Request" response Given new "CreateSecurityFilter" request