diff --git a/CHANGELOG.md b/CHANGELOG.md
index 443155405..ceb69f4f4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,12 @@
## [Unreleased]
+- Add the `alerts_filter` field to the `actions` in the Create Rule API ([#774](https://github.com/elastic/terraform-provider-elasticstack/pull/774))
- Add the `alert_delay` field to the Create Rule API ([#715](https://github.com/elastic/terraform-provider-elasticstack/pull/715))
- Add support for data_stream `lifecycle` template settings ([#724](https://github.com/elastic/terraform-provider-elasticstack/pull/724))
- Fix a provider panic when `elasticstack_kibana_action_connector` reads a non-existant connector ([#729](https://github.com/elastic/terraform-provider-elasticstack/pull/729))
- Add support for `remote_indicies` to `elasticstack_elasticsearch_security_role` & `elasticstack_kibana_security_role` (#723)[https://github.com/elastic/terraform-provider-elasticstack/pull/723]
- Fix error handling in `elasticstack_kibana_import_saved_objects` ([#738](https://github.com/elastic/terraform-provider-elasticstack/pull/738))
-- Remove `space_id` parameter from private locations to fix inconsistent state for `elasticstack_kibana_synthetics_private_location` `space_id` ([#733](https://github.com/elastic/terraform-provider-elasticstack/pull/733))
+- Remove `space_id` parameter from private locations to fix inconsistent state for `elasticstack_kibana_synthetics_private_location` `space_id` ([#733](https://github.com/elastic/terraform-provider-elasticstack/pull/733))
- Add the `Frequency` field to the Create Rule API ([#753](https://github.com/elastic/terraform-provider-elasticstack/pull/753))
- Prevent a provider panic when the repository referenced in an `elasticstack_elasticsearch_snapshot_repository` does not exist ([#758](https://github.com/elastic/terraform-provider-elasticstack/pull/758))
- Add support for `remote_indicies` to `elasticstack_elasticsearch_security_api_key` (#766)[https://github.com/elastic/terraform-provider-elasticstack/pull/766]
diff --git a/docs/resources/kibana_alerting_rule.md b/docs/resources/kibana_alerting_rule.md
index ee7deb0b7..8c25be398 100644
--- a/docs/resources/kibana_alerting_rule.md
+++ b/docs/resources/kibana_alerting_rule.md
@@ -86,9 +86,30 @@ Required:
Optional:
+- `alerts_filter` (Block List, Max: 1) Conditions that affect whether the action runs. If you specify multiple conditions, all conditions must be met for the action to run. For example, if an alert occurs within the specified time frame and matches the query, the action runs. (see [below for nested schema](#nestedblock--actions--alerts_filter))
- `frequency` (Block List, Max: 1) The properties that affect how often actions are generated. If the rule type supports setting summary to true, the action can be a summary of alerts at the specified notification interval. Otherwise, an action runs for each alert at the specified notification interval. NOTE: You cannot specify these parameters when `notify_when` or `throttle` are defined at the rule level. (see [below for nested schema](#nestedblock--actions--frequency))
- `group` (String) The group name, which affects when the action runs (for example, when the threshold is met or when the alert is recovered). Each rule type has a list of valid action group names.
+
+### Nested Schema for `actions.alerts_filter`
+
+Optional:
+
+- `kql` (String) Defines a query filter that determines whether the action runs. Written in Kibana Query Language (KQL).
+- `timeframe` (Block List, Max: 1) Defines a period that limits whether the action runs. (see [below for nested schema](#nestedblock--actions--alerts_filter--timeframe))
+
+
+### Nested Schema for `actions.alerts_filter.timeframe`
+
+Required:
+
+- `days` (List of Number) Defines the days of the week that the action can run, represented as an array of numbers. For example, 1 represents Monday. An empty array is equivalent to specifying all the days of the week.
+- `hours_end` (String) Defines the range of time in a day that the action can run. The end of the time frame in 24-hour notation (hh:mm).
+- `hours_start` (String) Defines the range of time in a day that the action can run. The start of the time frame in 24-hour notation (hh:mm).
+- `timezone` (String) The ISO time zone for the hours values. Values such as UTC and UTC+1 also work but lack built-in daylight savings time support and are not recommended.
+
+
+
### Nested Schema for `actions.frequency`
diff --git a/internal/clients/kibana/alerting.go b/internal/clients/kibana/alerting.go
index 61db39f35..5a1fe698a 100644
--- a/internal/clients/kibana/alerting.go
+++ b/internal/clients/kibana/alerting.go
@@ -30,13 +30,28 @@ func ruleResponseToModel(spaceID string, res *alerting.RuleResponseProperties) *
if !alerting.IsNil(action.Frequency) {
frequency := unwrapOptionalField(action.Frequency)
- a.Frequency = &models.AlertingRuleActionFrequency{
+ a.Frequency = &models.ActionFrequency{
Summary: frequency.Summary,
NotifyWhen: (string)(frequency.NotifyWhen),
Throttle: frequency.Throttle.Get(),
}
}
+ if !alerting.IsNil(action.AlertsFilter) {
+ filter := unwrapOptionalField(action.AlertsFilter)
+ timeframe := unwrapOptionalField(filter.Timeframe)
+
+ a.AlertsFilter = &models.ActionAlertsFilter{
+ Kql: *filter.Query.Kql,
+ Timeframe: models.AlertsFilterTimeframe{
+ Days: timeframe.Days,
+ Timezone: *timeframe.Timezone,
+ HoursStart: *timeframe.Hours.Start,
+ HoursEnd: *timeframe.Hours.End,
+ },
+ }
+ }
+
actions = append(actions, a)
}
@@ -100,6 +115,27 @@ func ruleActionsToActionsInner(ruleActions []models.AlertingRuleAction) []alerti
actionToAppend.Frequency = &frequency
}
+ if !alerting.IsNil(action.AlertsFilter) {
+ timeframe := action.AlertsFilter.Timeframe
+
+ filter := alerting.ActionsInnerAlertsFilter{
+ Query: &alerting.ActionsInnerAlertsFilterQuery{
+ Kql: &action.AlertsFilter.Kql,
+ Filters: []alerting.Filter{},
+ },
+ Timeframe: &alerting.ActionsInnerAlertsFilterTimeframe{
+ Timezone: &timeframe.Timezone,
+ Days: timeframe.Days,
+ Hours: &alerting.ActionsInnerAlertsFilterTimeframeHours{
+ Start: &timeframe.HoursStart,
+ End: &timeframe.HoursEnd,
+ },
+ },
+ }
+
+ actionToAppend.AlertsFilter = &filter
+ }
+
actions = append(actions, actionToAppend)
}
return actions
diff --git a/internal/clients/kibana/alerting_test.go b/internal/clients/kibana/alerting_test.go
index d1b87da43..f970b4a51 100644
--- a/internal/clients/kibana/alerting_test.go
+++ b/internal/clients/kibana/alerting_test.go
@@ -77,6 +77,19 @@ func Test_ruleResponseToModel(t *testing.T) {
NotifyWhen: "onThrottleInterval",
Throttle: *alerting.NewNullableString(utils.Pointer("10s")),
}),
+ AlertsFilter: utils.Pointer(alerting.ActionsInnerAlertsFilter{
+ Query: &alerting.ActionsInnerAlertsFilterQuery{
+ Kql: utils.Pointer("foobar"),
+ },
+ Timeframe: &alerting.ActionsInnerAlertsFilterTimeframe{
+ Days: []int32{3, 5, 7},
+ Timezone: utils.Pointer("UTC+1"),
+ Hours: &alerting.ActionsInnerAlertsFilterTimeframeHours{
+ Start: utils.Pointer("00:00"),
+ End: utils.Pointer("08:00"),
+ },
+ },
+ }),
},
{
Group: "group-2",
@@ -128,17 +141,26 @@ func Test_ruleResponseToModel(t *testing.T) {
Group: "group-1",
ID: "id",
Params: map[string]interface{}{},
- Frequency: &models.AlertingRuleActionFrequency{
+ Frequency: &models.ActionFrequency{
Summary: true,
NotifyWhen: "onThrottleInterval",
Throttle: utils.Pointer("10s"),
},
+ AlertsFilter: &models.ActionAlertsFilter{
+ Kql: "foobar",
+ Timeframe: models.AlertsFilterTimeframe{
+ Days: []int32{3, 5, 7},
+ Timezone: "UTC+1",
+ HoursStart: "00:00",
+ HoursEnd: "08:00",
+ },
+ },
},
{
Group: "group-2",
ID: "id",
Params: map[string]interface{}{},
- Frequency: &models.AlertingRuleActionFrequency{
+ Frequency: &models.ActionFrequency{
Summary: true,
NotifyWhen: "onActionGroupChange",
},
diff --git a/internal/kibana/alerting.go b/internal/kibana/alerting.go
index b8dfdc3d3..e92b7b568 100644
--- a/internal/kibana/alerting.go
+++ b/internal/kibana/alerting.go
@@ -16,10 +16,10 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
-var alertDelayMinSupportedVersion = version.Must(version.NewVersion("8.13.0"))
-
// when notify_when and throttle became optional
var frequencyMinSupportedVersion = version.Must(version.NewVersion("8.6.0"))
+var alertsFilterMinSupportedVersion = version.Must(version.NewVersion("8.9.0"))
+var alertDelayMinSupportedVersion = version.Must(version.NewVersion("8.13.0"))
func ResourceAlertingRule() *schema.Resource {
apikeySchema := map[string]*schema.Schema{
@@ -125,6 +125,59 @@ func ResourceAlertingRule() *schema.Resource {
},
},
},
+ "alerts_filter": {
+ Description: "Conditions that affect whether the action runs. If you specify multiple conditions, all conditions must be met for the action to run. For example, if an alert occurs within the specified time frame and matches the query, the action runs.",
+ Type: schema.TypeList,
+ MinItems: 0,
+ MaxItems: 1,
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "kql": {
+ Description: "Defines a query filter that determines whether the action runs. Written in Kibana Query Language (KQL).",
+ Type: schema.TypeString,
+ Optional: true,
+ },
+ "timeframe": {
+ Description: "Defines a period that limits whether the action runs.",
+ Type: schema.TypeList,
+ MinItems: 0,
+ MaxItems: 1,
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "days": {
+ Description: "Defines the days of the week that the action can run, represented as an array of numbers. For example, 1 represents Monday. An empty array is equivalent to specifying all the days of the week.",
+ Type: schema.TypeList,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeInt,
+ ValidateFunc: validation.IntBetween(1, 7),
+ },
+ },
+ "timezone": {
+ Description: "The ISO time zone for the hours values. Values such as UTC and UTC+1 also work but lack built-in daylight savings time support and are not recommended.",
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "hours_start": {
+ Description: "Defines the range of time in a day that the action can run. The start of the time frame in 24-hour notation (hh:mm).",
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: utils.StringIsHours,
+ },
+ "hours_end": {
+ Description: "Defines the range of time in a day that the action can run. The end of the time frame in 24-hour notation (hh:mm).",
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: utils.StringIsHours,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
},
},
},
@@ -288,7 +341,7 @@ func getActionsFromResourceData(d *schema.ResourceData, serverVersion *version.V
return []models.AlertingRuleAction{}, diag.Errorf("actions.frequency is only supported for Elasticsearch v8.6 or higher")
}
- frequency := models.AlertingRuleActionFrequency{
+ frequency := models.ActionFrequency{
Summary: d.Get(currentAction + ".frequency.0.summary").(bool),
NotifyWhen: d.Get(currentAction + ".frequency.0.notify_when").(string),
}
@@ -300,6 +353,34 @@ func getActionsFromResourceData(d *schema.ResourceData, serverVersion *version.V
a.Frequency = &frequency
}
+ if _, ok := d.GetOk(currentAction + ".alerts_filter"); ok {
+ if serverVersion.LessThan(alertsFilterMinSupportedVersion) {
+ return []models.AlertingRuleAction{}, diag.Errorf("actions.alerts_filter is only supported for Elasticsearch v8.9 or higher")
+ }
+
+ resourceDays := d.Get(currentAction + ".alerts_filter.0.timeframe.0.days").([]interface{})
+ days := []int32{}
+
+ for _, a := range resourceDays {
+ day := int32(a.(int))
+ days = append(days, day)
+ }
+
+ timeframe := models.AlertsFilterTimeframe{
+ Days: days,
+ Timezone: d.Get(currentAction + ".alerts_filter.0.timeframe.0.timezone").(string),
+ HoursStart: d.Get(currentAction + ".alerts_filter.0.timeframe.0.hours_start").(string),
+ HoursEnd: d.Get(currentAction + ".alerts_filter.0.timeframe.0.hours_end").(string),
+ }
+
+ filter := models.ActionAlertsFilter{
+ Kql: d.Get(currentAction + ".alerts_filter.0.kql").(string),
+ Timeframe: timeframe,
+ }
+
+ a.AlertsFilter = &filter
+ }
+
actions = append(actions, a)
}
}
@@ -454,11 +535,31 @@ func resourceRuleRead(ctx context.Context, d *schema.ResourceData, meta interfac
frequency = nil
}
+ alerts_filter := []interface{}{}
+
+ if action.AlertsFilter != nil {
+ timeframe := []interface{}{}
+ timeframe = append(timeframe, map[string]interface{}{
+ "days": action.AlertsFilter.Timeframe.Days,
+ "timezone": action.AlertsFilter.Timeframe.Timezone,
+ "hours_start": action.AlertsFilter.Timeframe.HoursStart,
+ "hours_end": action.AlertsFilter.Timeframe.HoursEnd,
+ })
+
+ alerts_filter = append(alerts_filter, map[string]interface{}{
+ "kql": action.AlertsFilter.Kql,
+ "timeframe": timeframe,
+ })
+ } else {
+ alerts_filter = nil
+ }
+
actions = append(actions, map[string]interface{}{
- "group": action.Group,
- "id": action.ID,
- "params": string(params),
- "frequency": frequency,
+ "group": action.Group,
+ "id": action.ID,
+ "params": string(params),
+ "frequency": frequency,
+ "alerts_filter": alerts_filter,
})
}
diff --git a/internal/kibana/alerting_test.go b/internal/kibana/alerting_test.go
index 385b273c1..0c0f3d37c 100644
--- a/internal/kibana/alerting_test.go
+++ b/internal/kibana/alerting_test.go
@@ -17,6 +17,8 @@ import (
func TestAccResourceAlertingRule(t *testing.T) {
minSupportedVersion := version.Must(version.NewSemver("7.14.0"))
+ minSupportedFrequencyVersion := version.Must(version.NewSemver("8.7.0"))
+ minSupportedAlertsFilterVersion := version.Must(version.NewSemver("8.9.0"))
minSupportedAlertDelayVersion := version.Must(version.NewSemver("8.13.0"))
t.Setenv("KIBANA_API_KEY", "")
@@ -57,32 +59,126 @@ func TestAccResourceAlertingRule(t *testing.T) {
),
},
{
- SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedAlertDelayVersion),
- Config: testAccResourceAlertingRuleWithAlertDelayCreate(ruleName),
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedFrequencyVersion),
+ Config: testAccResourceAlertingRuleWithFrequencyCreate(ruleName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "name", ruleName),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_id", "af22bd1c-8fb3-4020-9249-a4ac5471624b"),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "consumer", "alerts"),
- resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "notify_when", "onActiveAlert"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "notify_when", ""),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_type_id", ".index-threshold"),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "interval", "1m"),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "enabled", "true"),
- resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "alert_delay", "4"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.group", "threshold met"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.params", `{"documents":[{"message":"{{context.message}}","rule_id":"{{rule.id}}","rule_name":"{{rule.name}}"}]}`),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.summary", "true"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.notify_when", "onActionGroupChange"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.throttle", "10m"),
),
},
{
- SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedAlertDelayVersion),
- Config: testAccResourceAlertingRuleWithAlertDelayUpdate(ruleName),
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedFrequencyVersion),
+ Config: testAccResourceAlertingRuleWithFrequencyUpdate(ruleName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "name", fmt.Sprintf("Updated %s", ruleName)),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_id", "af22bd1c-8fb3-4020-9249-a4ac5471624b"),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "consumer", "alerts"),
- resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "notify_when", "onActiveAlert"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "notify_when", ""),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_type_id", ".index-threshold"),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "interval", "10m"),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "enabled", "false"),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "tags.0", "first"),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "tags.1", "second"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.group", "threshold met"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.params", `{"documents":[{"message":"{{context.message}} 3","rule_id":"{{rule.id}} 1","rule_name":"{{rule.name}} 2"}]}`),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.summary", "false"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.notify_when", "onActiveAlert"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.throttle", "2h"),
+ ),
+ },
+ {
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedAlertsFilterVersion),
+ Config: testAccResourceAlertingRuleWithAlertsFilterCreate(ruleName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "name", ruleName),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_id", "af22bd1c-8fb3-4020-9249-a4ac54716255"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "consumer", "alerts"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "notify_when", ""),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_type_id", "logs.alert.document.count"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "interval", "1m"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "enabled", "true"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.group", "logs.threshold.fired"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.params", `{"documents":[{"message":"{{context.message}}","rule_id":"{{rule.id}}","rule_name":"{{rule.name}}"}]}`),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.summary", "true"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.notify_when", "onActionGroupChange"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.throttle", "10m"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.kql", `kibana.alert.action_group: "slo.burnRate.alert" OR kibana.alert.action_group : "slo.burnRate.high"`),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.timeframe.0.days.0", "1"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.timeframe.0.days.1", "2"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.timeframe.0.days.2", "3"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.timeframe.0.timezone", "Africa/Accra"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.timeframe.0.hours_start", "01:00"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.timeframe.0.hours_end", "07:00"),
+ ),
+ },
+ {
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedAlertsFilterVersion),
+ Config: testAccResourceAlertingRuleWithAlertsFilterUpdate(ruleName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "name", ruleName),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_id", "af22bd1c-8fb3-4020-9249-a4ac54716255"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "consumer", "alerts"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "notify_when", ""),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_type_id", "logs.alert.document.count"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "interval", "1m"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "enabled", "true"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.group", "logs.threshold.fired"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.params", `{"documents":[{"message":"{{context.message}}","rule_id":"{{rule.id}}","rule_name":"{{rule.name}}"}]}`),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.summary", "true"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.notify_when", "onActionGroupChange"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.throttle", "10m"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.kql", `kibana.alert.action_group: "slo.burnRate.alert"`),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.timeframe.0.days.0", "7"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.timeframe.0.timezone", "Pacific/Honolulu"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.timeframe.0.hours_start", "02:00"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.alerts_filter.0.timeframe.0.hours_end", "03:00"),
+ ),
+ },
+ {
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedAlertDelayVersion),
+ Config: testAccResourceAlertingRuleWithAlertDelayCreate(ruleName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "name", ruleName),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_id", "af22bd1c-8fb3-4020-9249-a4ac54716255"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "consumer", "alerts"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "notify_when", ""),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_type_id", ".index-threshold"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "interval", "1m"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "enabled", "true"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.group", "threshold met"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.params", `{"documents":[{"message":"{{context.message}}","rule_id":"{{rule.id}}","rule_name":"{{rule.name}}"}]}`),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.summary", "true"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.notify_when", "onActionGroupChange"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.throttle", "10m"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "alert_delay", "4"),
+ ),
+ },
+ {
+ SkipFunc: versionutils.CheckIfVersionIsUnsupported(minSupportedAlertDelayVersion),
+ Config: testAccResourceAlertingRuleWithAlertDelayUpdate(ruleName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "name", ruleName),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_id", "af22bd1c-8fb3-4020-9249-a4ac54716255"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "consumer", "alerts"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "notify_when", ""),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "rule_type_id", ".index-threshold"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "interval", "1m"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "enabled", "true"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.group", "threshold met"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.params", `{"documents":[{"message":"{{context.message}}","rule_id":"{{rule.id}}","rule_name":"{{rule.name}}"}]}`),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.summary", "true"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.notify_when", "onActionGroupChange"),
+ resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "actions.0.frequency.0.throttle", "10m"),
resource.TestCheckResourceAttr("elasticstack_kibana_alerting_rule.test_rule", "alert_delay", "4"),
),
},
@@ -155,18 +251,28 @@ resource "elasticstack_kibana_alerting_rule" "test_rule" {
`, name)
}
-func testAccResourceAlertingRuleWithAlertDelayCreate(name string) string {
+// Frequency - v8.6.0
+
+func testAccResourceAlertingRuleWithFrequencyCreate(name string) string {
return fmt.Sprintf(`
provider "elasticstack" {
elasticsearch {}
kibana {}
}
+resource "elasticstack_kibana_action_connector" "index_example" {
+ name = "my_index_connector"
+ connector_type_id = ".index"
+ config = jsonencode({
+ index = "my-index"
+ executionTimeField = "alert_date"
+ })
+}
+
resource "elasticstack_kibana_alerting_rule" "test_rule" {
name = "%s"
rule_id = "af22bd1c-8fb3-4020-9249-a4ac5471624b"
consumer = "alerts"
- notify_when = "onActiveAlert"
params = jsonencode({
aggType = "avg"
groupBy = "top"
@@ -183,23 +289,48 @@ resource "elasticstack_kibana_alerting_rule" "test_rule" {
rule_type_id = ".index-threshold"
interval = "1m"
enabled = true
- alert_delay = 4
+
+ actions {
+ id = elasticstack_kibana_action_connector.index_example.connector_id
+ group = "threshold met"
+ params = jsonencode({
+ "documents" : [{
+ "rule_id" : "{{rule.id}}",
+ "rule_name" : "{{rule.name}}",
+ "message" : "{{context.message}}"
+ }]
+ })
+
+ frequency {
+ summary = true
+ notify_when = "onActionGroupChange"
+ throttle = "10m"
+ }
+ }
}
`, name)
}
-func testAccResourceAlertingRuleWithAlertDelayUpdate(name string) string {
+func testAccResourceAlertingRuleWithFrequencyUpdate(name string) string {
return fmt.Sprintf(`
provider "elasticstack" {
elasticsearch {}
kibana {}
}
+resource "elasticstack_kibana_action_connector" "index_example" {
+ name = "my_index_connector"
+ connector_type_id = ".index"
+ config = jsonencode({
+ index = "my-index"
+ executionTimeField = "alert_date"
+ })
+ }
+
resource "elasticstack_kibana_alerting_rule" "test_rule" {
name = "Updated %s"
rule_id = "af22bd1c-8fb3-4020-9249-a4ac5471624b"
consumer = "alerts"
- notify_when = "onActiveAlert"
params = jsonencode({
aggType = "avg"
groupBy = "top"
@@ -217,6 +348,297 @@ resource "elasticstack_kibana_alerting_rule" "test_rule" {
interval = "10m"
enabled = false
tags = ["first", "second"]
+
+ actions {
+ id = elasticstack_kibana_action_connector.index_example.connector_id
+ group = "threshold met"
+ params = jsonencode({
+ "documents" : [{
+ "rule_id" : "{{rule.id}} 1",
+ "rule_name" : "{{rule.name}} 2",
+ "message" : "{{context.message}} 3"
+ }]
+ })
+
+ frequency {
+ summary = false
+ notify_when = "onActiveAlert"
+ throttle = "2h"
+ }
+ }
+}
+ `, name)
+}
+
+// Alerts Filter - v8.9.0
+
+func testAccResourceAlertingRuleWithAlertsFilterCreate(name string) string {
+ return fmt.Sprintf(`
+provider "elasticstack" {
+ elasticsearch {}
+ kibana {}
+}
+
+resource "elasticstack_kibana_action_connector" "index_example" {
+ name = "my_index_connector"
+ connector_type_id = ".index"
+ config = jsonencode({
+ index = "my-index"
+ executionTimeField = "alert_date"
+ })
+}
+
+resource "elasticstack_kibana_alerting_rule" "test_rule" {
+ name = "%s"
+ rule_id = "af22bd1c-8fb3-4020-9249-a4ac54716255"
+ consumer = "alerts"
+ params = jsonencode({
+ "timeSize": 5,
+ "timeUnit": "m",
+ "logView": {
+ "type": "log-view-reference",
+ "logViewId": "default"
+ },
+ "count": {
+ "value": 75,
+ "comparator": "more than"
+ },
+ "criteria": [
+ {
+ "field": "_id",
+ "comparator": "matches",
+ "value": "33"
+ }
+ ]
+ })
+ rule_type_id = "logs.alert.document.count"
+ interval = "1m"
+ enabled = true
+
+ actions {
+ id = elasticstack_kibana_action_connector.index_example.connector_id
+ group = "logs.threshold.fired"
+ params = jsonencode({
+ "documents" : [{
+ "rule_id" : "{{rule.id}}",
+ "rule_name" : "{{rule.name}}",
+ "message" : "{{context.message}}"
+ }]
+ })
+
+ frequency {
+ summary = true
+ notify_when = "onActionGroupChange"
+ throttle = "10m"
+ }
+
+ alerts_filter {
+ kql = "kibana.alert.action_group: \"slo.burnRate.alert\" OR kibana.alert.action_group : \"slo.burnRate.high\""
+
+ timeframe {
+ days = [1,2,3]
+ timezone = "Africa/Accra"
+ hours_start = "01:00"
+ hours_end = "07:00"
+ }
+ }
+ }
+}
+ `, name)
+}
+
+func testAccResourceAlertingRuleWithAlertsFilterUpdate(name string) string {
+ return fmt.Sprintf(`
+provider "elasticstack" {
+ elasticsearch {}
+ kibana {}
+}
+
+resource "elasticstack_kibana_action_connector" "index_example" {
+ name = "my_index_connector"
+ connector_type_id = ".index"
+ config = jsonencode({
+ index = "my-index"
+ executionTimeField = "alert_date"
+ })
+}
+
+resource "elasticstack_kibana_alerting_rule" "test_rule" {
+ name = "%s"
+ rule_id = "af22bd1c-8fb3-4020-9249-a4ac54716255"
+ consumer = "alerts"
+ params = jsonencode({
+ "timeSize": 5,
+ "timeUnit": "m",
+ "logView": {
+ "type": "log-view-reference",
+ "logViewId": "default"
+ },
+ "count": {
+ "value": 75,
+ "comparator": "more than"
+ },
+ "criteria": [
+ {
+ "field": "_id",
+ "comparator": "matches",
+ "value": "33"
+ }
+ ]
+ })
+ rule_type_id = "logs.alert.document.count"
+ interval = "1m"
+ enabled = true
+
+ actions {
+ id = elasticstack_kibana_action_connector.index_example.connector_id
+ group = "logs.threshold.fired"
+ params = jsonencode({
+ "documents" : [{
+ "rule_id" : "{{rule.id}}",
+ "rule_name" : "{{rule.name}}",
+ "message" : "{{context.message}}"
+ }]
+ })
+
+ frequency {
+ summary = true
+ notify_when = "onActionGroupChange"
+ throttle = "10m"
+ }
+
+ alerts_filter {
+ kql = "kibana.alert.action_group: \"slo.burnRate.alert\""
+
+ timeframe {
+ days = [7]
+ timezone = "Pacific/Honolulu"
+ hours_start = "02:00"
+ hours_end = "03:00"
+ }
+ }
+ }
+}
+ `, name)
+}
+
+// Alert Delay - v8.13.0
+
+func testAccResourceAlertingRuleWithAlertDelayCreate(name string) string {
+ return fmt.Sprintf(`
+provider "elasticstack" {
+ elasticsearch {}
+ kibana {}
+}
+
+resource "elasticstack_kibana_action_connector" "index_example" {
+ name = "my_index_connector"
+ connector_type_id = ".index"
+ config = jsonencode({
+ index = "my-index"
+ executionTimeField = "alert_date"
+ })
+}
+
+resource "elasticstack_kibana_alerting_rule" "test_rule" {
+ name = "%s"
+ rule_id = "af22bd1c-8fb3-4020-9249-a4ac54716255"
+ consumer = "alerts"
+ params = jsonencode({
+ aggType = "avg"
+ groupBy = "top"
+ termSize = 10
+ timeWindowSize = 10
+ timeWindowUnit = "s"
+ threshold = [10]
+ thresholdComparator = ">"
+ index = ["test-index"]
+ timeField = "@timestamp"
+ aggField = "version"
+ termField = "name"
+ })
+ rule_type_id = ".index-threshold"
+ interval = "1m"
+ enabled = true
+
+ actions {
+ id = elasticstack_kibana_action_connector.index_example.connector_id
+ group = "threshold met"
+ params = jsonencode({
+ "documents" : [{
+ "rule_id" : "{{rule.id}}",
+ "rule_name" : "{{rule.name}}",
+ "message" : "{{context.message}}"
+ }]
+ })
+
+ frequency {
+ summary = true
+ notify_when = "onActionGroupChange"
+ throttle = "10m"
+ }
+ }
+
+ alert_delay = 4
+}
+ `, name)
+}
+
+func testAccResourceAlertingRuleWithAlertDelayUpdate(name string) string {
+ return fmt.Sprintf(`
+provider "elasticstack" {
+ elasticsearch {}
+ kibana {}
+}
+
+resource "elasticstack_kibana_action_connector" "index_example" {
+ name = "my_index_connector"
+ connector_type_id = ".index"
+ config = jsonencode({
+ index = "my-index"
+ executionTimeField = "alert_date"
+ })
+}
+
+resource "elasticstack_kibana_alerting_rule" "test_rule" {
+ name = "%s"
+ rule_id = "af22bd1c-8fb3-4020-9249-a4ac54716255"
+ consumer = "alerts"
+ params = jsonencode({
+ aggType = "avg"
+ groupBy = "top"
+ termSize = 10
+ timeWindowSize = 10
+ timeWindowUnit = "s"
+ threshold = [10]
+ thresholdComparator = ">"
+ index = ["test-index"]
+ timeField = "@timestamp"
+ aggField = "version"
+ termField = "name"
+ })
+ rule_type_id = ".index-threshold"
+ interval = "1m"
+ enabled = true
+
+ actions {
+ id = elasticstack_kibana_action_connector.index_example.connector_id
+ group = "threshold met"
+ params = jsonencode({
+ "documents" : [{
+ "rule_id" : "{{rule.id}}",
+ "rule_name" : "{{rule.name}}",
+ "message" : "{{context.message}}"
+ }]
+ })
+
+ frequency {
+ summary = true
+ notify_when = "onActionGroupChange"
+ throttle = "10m"
+ }
+ }
+
alert_delay = 4
}
`, name)
diff --git a/internal/models/alert_rule.go b/internal/models/alert_rule.go
index 31378c8c8..c8d05d074 100644
--- a/internal/models/alert_rule.go
+++ b/internal/models/alert_rule.go
@@ -28,10 +28,11 @@ type AlertingRuleSchedule struct {
}
type AlertingRuleAction struct {
- Group string
- ID string
- Params map[string]interface{}
- Frequency *AlertingRuleActionFrequency
+ Group string
+ ID string
+ Params map[string]interface{}
+ Frequency *ActionFrequency
+ AlertsFilter *ActionAlertsFilter
}
type AlertingRuleExecutionStatus struct {
@@ -39,8 +40,20 @@ type AlertingRuleExecutionStatus struct {
Status *string
}
-type AlertingRuleActionFrequency struct {
+type ActionFrequency struct {
Summary bool
NotifyWhen string
Throttle *string
}
+
+type ActionAlertsFilter struct {
+ Kql string
+ Timeframe AlertsFilterTimeframe
+}
+
+type AlertsFilterTimeframe struct {
+ Days []int32
+ Timezone string
+ HoursStart string
+ HoursEnd string
+}
diff --git a/internal/utils/validation.go b/internal/utils/validation.go
index 04a367482..379548dec 100644
--- a/internal/utils/validation.go
+++ b/internal/utils/validation.go
@@ -40,3 +40,23 @@ func StringIsElasticDuration(i interface{}, k string) (warnings []string, errors
return nil, nil
}
+
+// StringIsHours is a SchemaValidateFunc which tests to make sure the supplied string is in the required format of HH:mm
+func StringIsHours(i interface{}, k string) (warnings []string, errors []error) {
+ v, ok := i.(string)
+ if !ok {
+ return nil, []error{fmt.Errorf("expected type of %s to be string", k)}
+ }
+
+ if v == "" {
+ return nil, []error{fmt.Errorf("%q string is not a valid time in HH:mm format: [empty]", k)}
+ }
+
+ r := regexp.MustCompile(`^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$`)
+
+ if !r.MatchString(v) {
+ return nil, []error{fmt.Errorf("%q string is not a valid time in HH:mm format", k)}
+ }
+
+ return nil, nil
+}