diff --git a/sysdig/internal/client/v2/alerts_v2.go b/sysdig/internal/client/v2/alerts_v2.go index 184b26bd..8a8e085a 100644 --- a/sysdig/internal/client/v2/alerts_v2.go +++ b/sysdig/internal/client/v2/alerts_v2.go @@ -29,6 +29,7 @@ const ( AlertV2TypeEvent AlertV2Type = "EVENT" AlertV2TypeChange AlertV2Type = "PERCENTAGE_OF_CHANGE" AlertV2TypeFormBasedPrometheus AlertV2Type = "FORM_BASED_PROMETHEUS" + AlertV2TypeGroupOutlier AlertV2Type = "GROUP_OUTLIERS" AlertV2SeverityHigh AlertV2Severity = "high" AlertV2SeverityMedium AlertV2Severity = "medium" @@ -52,6 +53,7 @@ type AlertV2Interface interface { AlertV2DowntimeInterface AlertV2ChangeInterface AlertV2FormBasedPrometheusInterface + AlertV2GroupOutlierInterface } type AlertV2PrometheusInterface interface { @@ -94,6 +96,14 @@ type AlertV2FormBasedPrometheusInterface interface { DeleteAlertV2FormBasedPrometheus(ctx context.Context, alertID int) error } +type AlertV2GroupOutlierInterface interface { + Base + CreateAlertV2GroupOutlier(ctx context.Context, alert AlertV2GroupOutlier) (AlertV2GroupOutlier, error) + UpdateAlertV2GroupOutlier(ctx context.Context, alert AlertV2GroupOutlier) (AlertV2GroupOutlier, error) + GetAlertV2GroupOutlier(ctx context.Context, alertID int) (AlertV2GroupOutlier, error) + DeleteAlertV2GroupOutlier(ctx context.Context, alertID int) error +} + type AlertV2DowntimeInterface interface { Base CreateAlertV2Downtime(ctx context.Context, alert AlertV2Downtime) (AlertV2Downtime, error) @@ -547,6 +557,82 @@ func (client *Client) DeleteAlertV2FormBasedPrometheus(ctx context.Context, aler return client.deleteAlertV2(ctx, alertID) } +func (client *Client) CreateAlertV2GroupOutlier(ctx context.Context, alert AlertV2GroupOutlier) (AlertV2GroupOutlier, error) { + err := client.addNotificationChannelType(ctx, alert.NotificationChannelConfigList) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + err = client.translateScopeSegmentLabels(ctx, &alert.Config.ScopedSegmentedConfig) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + payload, err := Marshal(alertV2GroupOutlierWrapper{Alert: alert}) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + body, err := client.createAlertV2(ctx, payload) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + wrapper, err := Unmarshal[alertV2GroupOutlierWrapper](body) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + return wrapper.Alert, nil +} + +func (client *Client) UpdateAlertV2GroupOutlier(ctx context.Context, alert AlertV2GroupOutlier) (AlertV2GroupOutlier, error) { + err := client.addNotificationChannelType(ctx, alert.NotificationChannelConfigList) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + err = client.translateScopeSegmentLabels(ctx, &alert.Config.ScopedSegmentedConfig) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + payload, err := Marshal(alertV2GroupOutlierWrapper{Alert: alert}) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + body, err := client.updateAlertV2(ctx, alert.ID, payload) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + wrapper, err := Unmarshal[alertV2GroupOutlierWrapper](body) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + return wrapper.Alert, nil +} + +func (client *Client) GetAlertV2GroupOutlier(ctx context.Context, alertID int) (AlertV2GroupOutlier, error) { + body, err := client.getAlertV2(ctx, alertID) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + wrapper, err := Unmarshal[alertV2GroupOutlierWrapper](body) + if err != nil { + return AlertV2GroupOutlier{}, err + } + + return wrapper.Alert, nil +} + +func (client *Client) DeleteAlertV2GroupOutlier(ctx context.Context, alertID int) error { + return client.deleteAlertV2(ctx, alertID) +} + func (client *Client) createAlertV2(ctx context.Context, alertJson io.Reader) (io.ReadCloser, error) { response, err := client.requester.Request(ctx, http.MethodPost, client.alertsV2URL(), alertJson) if err != nil { diff --git a/sysdig/internal/client/v2/model.go b/sysdig/internal/client/v2/model.go index cb91fefe..dbed0ffb 100644 --- a/sysdig/internal/client/v2/model.go +++ b/sysdig/internal/client/v2/model.go @@ -681,6 +681,31 @@ type alertV2FormBasedPrometheusWrapper struct { Alert AlertV2FormBasedPrometheus `json:"alert"` } +type AlertV2ConfigGroupOutlier struct { + ScopedSegmentedConfig + + Algorithm string `json:"algorithm"` + MadThreshold float64 `json:"madThreshold,omitempty"` + MadTolerance float64 `json:"madTolerance,omitempty"` + DbscanTolerance float64 `json:"dbscanTolerance,omitempty"` + + GroupAggregation string `json:"groupAggregation"` + TimeAggregation string `json:"timeAggregation"` + Metric AlertMetricDescriptorV2 `json:"metric"` + NoDataBehaviour string `json:"noDataBehaviour"` +} + +type AlertV2GroupOutlier struct { + AlertV2Common + DurationSec int `json:"durationSec"` // Observation window should be greater than or equal to 10 minutes + Config AlertV2ConfigGroupOutlier `json:"config"` + UnreportedAlertNotificationsRetentionSec *int `json:"unreportedAlertNotificationsRetentionSec"` +} + +type alertV2GroupOutlierWrapper struct { + Alert AlertV2GroupOutlier `json:"alert"` +} + type AlertV2Change struct { AlertV2Common DurationSec int `json:"durationSec"` // not really used but the api wants it set to 0 in POST/PUT diff --git a/sysdig/provider.go b/sysdig/provider.go index 336ab8c3..f4fc6f1e 100644 --- a/sysdig/provider.go +++ b/sysdig/provider.go @@ -164,6 +164,7 @@ func (p *SysdigProvider) Provider() *schema.Provider { "sysdig_monitor_alert_v2_prometheus": resourceSysdigMonitorAlertV2Prometheus(), "sysdig_monitor_alert_v2_change": resourceSysdigMonitorAlertV2Change(), "sysdig_monitor_alert_v2_form_based_prometheus": resourceSysdigMonitorAlertV2FormBasedPrometheus(), + "sysdig_monitor_alert_v2_group_outlier": resourceSysdigMonitorAlertV2GroupOutlier(), "sysdig_monitor_dashboard": resourceSysdigMonitorDashboard(), "sysdig_monitor_notification_channel_email": resourceSysdigMonitorNotificationChannelEmail(), "sysdig_monitor_notification_channel_opsgenie": resourceSysdigMonitorNotificationChannelOpsGenie(), diff --git a/sysdig/resource_sysdig_monitor_alert_v2_form_based_prometheus_test.go b/sysdig/resource_sysdig_monitor_alert_v2_form_based_prometheus_test.go index 5a571e7b..d5111a5a 100644 --- a/sysdig/resource_sysdig_monitor_alert_v2_form_based_prometheus_test.go +++ b/sysdig/resource_sysdig_monitor_alert_v2_form_based_prometheus_test.go @@ -51,6 +51,9 @@ func TestAccAlertV2FormBasedPrometheusTest(t *testing.T) { { Config: alertV2FormBasedPrometheusTestWithEnabled(rText()), }, + { + Config: alertV2FormBasedWithUnreportedAlertNotificationsRetentionSec(rText()), + }, { Config: alertV2FormBasedPrometheusTestWithWarningThreshold(rText()), }, @@ -232,6 +235,18 @@ resource "sysdig_monitor_alert_v2_form_based_prometheus" "sample" { `, name) } +func alertV2FormBasedWithUnreportedAlertNotificationsRetentionSec(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_v2_form_based_prometheus" "sample" { + name = "TERRAFORM TEST - FORM BASED PROMETHEUS %s" + query = "avg_over_time(sysdig_container_cpu_used_percent{container_name=\"test\"}[59s])" + operator = ">=" + threshold = 50 + unreported_alert_notifications_retention_seconds = 60 * 60 * 24 * 30 +} +`, name) +} + func alertV2FormBasedPrometheusTestWithWarningThreshold(name string) string { return fmt.Sprintf(` resource "sysdig_monitor_notification_channel_email" "nc_email_3" { diff --git a/sysdig/resource_sysdig_monitor_alert_v2_group_outlier.go b/sysdig/resource_sysdig_monitor_alert_v2_group_outlier.go new file mode 100644 index 00000000..6386cab0 --- /dev/null +++ b/sysdig/resource_sysdig_monitor_alert_v2_group_outlier.go @@ -0,0 +1,288 @@ +package sysdig + +import ( + "context" + "fmt" + "strconv" + "time" + + v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceSysdigMonitorAlertV2GroupOutlier() *schema.Resource { + timeout := 5 * time.Minute + + resource := &schema.Resource{ + CreateContext: resourceSysdigMonitorAlertV2GroupOutlierCreate, + UpdateContext: resourceSysdigMonitorAlertV2GroupOutlierUpdate, + ReadContext: resourceSysdigMonitorAlertV2GroupOutlierRead, + DeleteContext: resourceSysdigMonitorAlertV2GroupOutlierDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(timeout), + Update: schema.DefaultTimeout(timeout), + Read: schema.DefaultTimeout(timeout), + Delete: schema.DefaultTimeout(timeout), + }, + + Schema: createScopedSegmentedAlertV2Schema(createAlertV2Schema(map[string]*schema.Schema{ + "observation_window_minutes": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(10), + }, + "algorithm": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"DBSCAN", "MAD"}, false), + }, + "mad_threshold": { + Type: schema.TypeFloat, + Optional: true, + ValidateFunc: validation.FloatBetween(1, 100), + }, + "mad_tolerance": { + Type: schema.TypeFloat, + Optional: true, + ValidateFunc: validation.FloatBetween(0.5, 10), + }, + "dbscan_tolerance": { + Type: schema.TypeFloat, + Optional: true, + ValidateFunc: validation.FloatBetween(0.5, 10), + }, + "metric": { + Type: schema.TypeString, + Required: true, + }, + "time_aggregation": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"avg", "timeAvg", "sum", "min", "max"}, false), + }, + "group_aggregation": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"avg", "sum", "min", "max"}, false), + }, + "no_data_behaviour": { + Type: schema.TypeString, + Optional: true, + Default: "DO_NOTHING", + ValidateFunc: validation.StringInSlice([]string{"DO_NOTHING", "TRIGGER"}, false), + }, + "unreported_alert_notifications_retention_seconds": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(60), + }, + })), + + CustomizeDiff: func(ctx context.Context, diff *schema.ResourceDiff, i interface{}) error { + algorithm := diff.Get("algorithm").(string) + madThreshold := diff.Get("mad_threshold").(float64) + madTolerance := diff.Get("mad_tolerance").(float64) + dbscanTolerance := diff.Get("dbscan_tolerance").(float64) + + if algorithm == "MAD" && madThreshold == 0 && madTolerance == 0 { + return fmt.Errorf("mad_threshold and mad_tolerance must both be defined and non zero if algorithm = MAD") + } + if algorithm == "DBSCAN" && dbscanTolerance == 0 { + return fmt.Errorf("dbscan_tolerance must be defined and non zero if algorithm = DBSCAN") + } + return nil + }, + } + // group outlier alert type must be segmented + resource.Schema["group_by"].Optional = false + resource.Schema["group_by"].Required = true + + return resource +} + +func getAlertV2GroupOutlierClient(c SysdigClients) (v2.AlertV2GroupOutlierInterface, error) { + return getAlertV2Client(c) +} + +func resourceSysdigMonitorAlertV2GroupOutlierCreate(ctx context.Context, d *schema.ResourceData, i interface{}) diag.Diagnostics { + client, err := getAlertV2GroupOutlierClient(i.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + a, err := buildAlertV2GroupOutlierStruct(d) + if err != nil { + return diag.FromErr(err) + } + + aCreated, err := client.CreateAlertV2GroupOutlier(ctx, *a) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(strconv.Itoa(aCreated.ID)) + + err = updateAlertV2GroupOutlierState(d, &aCreated) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigMonitorAlertV2GroupOutlierRead(ctx context.Context, d *schema.ResourceData, i interface{}) diag.Diagnostics { + client, err := getAlertV2GroupOutlierClient(i.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + a, err := client.GetAlertV2GroupOutlier(ctx, id) + if err != nil { + if err == v2.AlertV2NotFound { + d.SetId("") + return nil + } + return diag.FromErr(err) + } + + err = updateAlertV2GroupOutlierState(d, &a) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigMonitorAlertV2GroupOutlierUpdate(ctx context.Context, d *schema.ResourceData, i interface{}) diag.Diagnostics { + client, err := getAlertV2GroupOutlierClient(i.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + a, err := buildAlertV2GroupOutlierStruct(d) + if err != nil { + return diag.FromErr(err) + } + + a.ID, _ = strconv.Atoi(d.Id()) + + aUpdated, err := client.UpdateAlertV2GroupOutlier(ctx, *a) + if err != nil { + return diag.FromErr(err) + } + + err = updateAlertV2GroupOutlierState(d, &aUpdated) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigMonitorAlertV2GroupOutlierDelete(ctx context.Context, d *schema.ResourceData, i interface{}) diag.Diagnostics { + client, err := getAlertV2GroupOutlierClient(i.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + err = client.DeleteAlertV2GroupOutlier(ctx, id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func buildAlertV2GroupOutlierStruct(d *schema.ResourceData) (*v2.AlertV2GroupOutlier, error) { + alertV2Common := buildAlertV2CommonStruct(d) + alertV2Common.Type = string(v2.AlertV2TypeGroupOutlier) + config := v2.AlertV2ConfigGroupOutlier{} + + buildScopedSegmentedConfigStruct(d, &config.ScopedSegmentedConfig) + + config.Algorithm = d.Get("algorithm").(string) + + config.MadThreshold = d.Get("mad_threshold").(float64) + + config.MadTolerance = d.Get("mad_tolerance").(float64) + + config.DbscanTolerance = d.Get("dbscan_tolerance").(float64) + + metric := d.Get("metric").(string) + config.Metric.ID = metric + + config.TimeAggregation = d.Get("time_aggregation").(string) + + config.GroupAggregation = d.Get("group_aggregation").(string) + + config.NoDataBehaviour = d.Get("no_data_behaviour").(string) + + var unreportedAlertNotificationsRetentionSec *int + if unreportedAlertNotificationsRetentionSecInterface, ok := d.GetOk("unreported_alert_notifications_retention_seconds"); ok { + u := unreportedAlertNotificationsRetentionSecInterface.(int) + unreportedAlertNotificationsRetentionSec = &u + } + + alert := &v2.AlertV2GroupOutlier{ + AlertV2Common: *alertV2Common, + DurationSec: minutesToSeconds(d.Get("observation_window_minutes").(int)), + Config: config, + UnreportedAlertNotificationsRetentionSec: unreportedAlertNotificationsRetentionSec, + } + return alert, nil +} + +func updateAlertV2GroupOutlierState(d *schema.ResourceData, alert *v2.AlertV2GroupOutlier) error { + err := updateAlertV2CommonState(d, &alert.AlertV2Common) + if err != nil { + return err + } + + err = updateScopedSegmentedConfigState(d, &alert.Config.ScopedSegmentedConfig) + if err != nil { + return err + } + + _ = d.Set("observation_window_minutes", secondsToMinutes(alert.DurationSec)) + + _ = d.Set("algorithm", alert.Config.Algorithm) + + _ = d.Set("mad_threshold", alert.Config.MadThreshold) + + _ = d.Set("mad_tolerance", alert.Config.MadTolerance) + + _ = d.Set("dbscan_tolerance", alert.Config.DbscanTolerance) + + _ = d.Set("metric", alert.Config.Metric.ID) + + _ = d.Set("time_aggregation", alert.Config.TimeAggregation) + + _ = d.Set("group_aggregation", alert.Config.GroupAggregation) + + _ = d.Set("no_data_behaviour", alert.Config.NoDataBehaviour) + + if alert.UnreportedAlertNotificationsRetentionSec != nil { + _ = d.Set("unreported_alert_notifications_retention_seconds", *alert.UnreportedAlertNotificationsRetentionSec) + } else { + _ = d.Set("unreported_alert_notifications_retention_seconds", nil) + } + + return nil +} diff --git a/sysdig/resource_sysdig_monitor_alert_v2_group_outlier_test.go b/sysdig/resource_sysdig_monitor_alert_v2_group_outlier_test.go new file mode 100644 index 00000000..c99a4bbc --- /dev/null +++ b/sysdig/resource_sysdig_monitor_alert_v2_group_outlier_test.go @@ -0,0 +1,464 @@ +//go:build tf_acc_sysdig_monitor || tf_acc_ibm_monitor + +package sysdig_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/draios/terraform-provider-sysdig/sysdig" +) + +func TestAccAlertV2GroupOutlier(t *testing.T) { + rText := func() string { return acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheckAnyEnv(t, SysdigMonitorApiTokenEnv, SysdigIBMMonitorAPIKeyEnv), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: alertV2GroupOutlier(rText()), + }, + { + Config: alertV2GroupOutlierWithMAD(rText()), + }, + { + Config: alertV2GroupOutlierWithNoData(rText()), + }, + { + Config: alertV2GroupOutlierWithNotificationChannels(rText()), + }, + { + Config: alertV2GroupOutlierWithDescription(rText()), + }, + { + Config: alertV2GroupOutlierWithSeverity(rText()), + }, + { + Config: alertV2GroupOutlierWithGroup(rText()), + }, + { + Config: alertV2GroupOutlierWithCustomNotifications(rText()), + }, + { + Config: alertV2GroupOutlierWithCapture(rText()), + }, + { + Config: alertV2GroupOutlierWithLink(rText()), + }, + { + Config: alertV2GroupOutlierWithEnabled(rText()), + }, + { + Config: alertV2GroupOutlierWithUnreportedAlertNotificationsRetentionSec(rText()), + }, + { + ResourceName: "sysdig_monitor_alert_v2_group_outlier.sample", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func alertV2GroupOutlier(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "DBSCAN" + dbscan_tolerance = 1.2 + observation_window_minutes = 15 + +} +`, name) +} + +func alertV2GroupOutlierWithMAD(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "MAD" + mad_threshold = 10.1 + mad_tolerance = 5.5 + observation_window_minutes = 15 + +} +`, name) +} + +func alertV2GroupOutlierWithNoData(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "DBSCAN" + dbscan_tolerance = 1 + observation_window_minutes = 15 + no_data_behaviour = "TRIGGER" + +} +`, name) +} + +func alertV2GroupOutlierWithNotificationChannels(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_notification_channel_email" "nc_email1" { + name = "%s1" + recipients = ["root@localhost.com"] +} + +resource "sysdig_monitor_notification_channel_email" "nc_email2" { + name = "%s2" + recipients = ["root@localhost.com"] +} + +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "DBSCAN" + dbscan_tolerance = 1 + observation_window_minutes = 15 + enabled = false + notification_channels { + id = sysdig_monitor_notification_channel_email.nc_email1.id + notify_on_resolve = false + } + notification_channels { + id = sysdig_monitor_notification_channel_email.nc_email2.id + renotify_every_minutes = 30 + } +} +`, name, name, name) +} + +func alertV2GroupOutlierWithDescription(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "DBSCAN" + dbscan_tolerance = 1 + observation_window_minutes = 15 + description = "description" + +} +`, name) +} + +func alertV2GroupOutlierWithSeverity(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "DBSCAN" + dbscan_tolerance = 1 + observation_window_minutes = 15 + severity = "high" + +} +`, name) +} + +func alertV2GroupOutlierWithGroup(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "DBSCAN" + dbscan_tolerance = 1 + observation_window_minutes = 15 + group = "customgroup" + +} +`, name) +} + +func alertV2GroupOutlierWithCustomNotifications(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "DBSCAN" + dbscan_tolerance = 1 + observation_window_minutes = 15 + custom_notification { + subject = "test" + prepend = "pre" + append = "post" + } + +} +`, name) +} + +func alertV2GroupOutlierWithCapture(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "DBSCAN" + dbscan_tolerance = 1 + observation_window_minutes = 15 + capture { + filename = "test.scap" + } +} +`, name) +} + +func alertV2GroupOutlierWithLink(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_dashboard" "dashboard" { + name = "TERRAFORM TEST - METRIC %s" + description = "TERRAFORM TEST - METRIC %s" + + panel { + pos_x = 0 + pos_y = 0 + width = 12 # Maximum size: 24 + height = 6 + type = "timechart" + name = "example panel" + description = "description" + + legend { + show_current = true + position = "bottom" + layout = "inline" + } + + query { + promql = "avg(avg_over_time(sysdig_host_cpu_used_percent[$__interval]))" + unit = "percent" + + format { + display_format = "auto" + input_format = "0-100" + y_axis = "auto" + null_value_display_mode = "nullGap" + } + } + } +} + +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "DBSCAN" + dbscan_tolerance = 1 + observation_window_minutes = 15 + link { + type = "runbook" + href = "http://example.com" + } + link { + type = "dashboard" + id = sysdig_monitor_dashboard.dashboard.id + } +} +`, name, name, name) +} + +func alertV2GroupOutlierWithEnabled(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "DBSCAN" + dbscan_tolerance = 1 + observation_window_minutes = 15 + enabled = false + +} +`, name) +} + +func alertV2GroupOutlierWithUnreportedAlertNotificationsRetentionSec(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "TERRAFORM TEST - GROUP OUTLIER %s" + metric = "sysdig_container_cpu_used_percent" + group_aggregation = "avg" + time_aggregation = "avg" + group_by = ["kube_cluster_name", "cloud_provider_tag_Owner",] + scope { + label = "kube_cluster_name" + operator = "in" + values = ["thom-cluster1", "demo-env-prom"] + } + scope { + label = "kube_cluster_name" + operator = "equals" + values = ["thom-cluster3"] + } + algorithm = "DBSCAN" + dbscan_tolerance = 1 + observation_window_minutes = 15 + unreported_alert_notifications_retention_seconds = 60 * 60 * 24 * 30 +} +`, name) +} diff --git a/website/docs/index.md b/website/docs/index.md index 824b05ce..a2fdc891 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -249,6 +249,7 @@ When IBM Workload Protection resources are to be created, this authentication mu > - `sysdig_monitor_alert_v2_prometheus` > - `sysdig_monitor_alert_v2_change` > - `sysdig_monitor_alert_v2_form_based_prometheus` +> - `sysdig_monitor_alert_v2_group_outlier` > - `sysdig_monitor_dashboard` > - `sysdig_secure_posture_zone` > diff --git a/website/docs/r/monitor_alert_v2_group_outlier.md b/website/docs/r/monitor_alert_v2_group_outlier.md new file mode 100644 index 00000000..05bd1090 --- /dev/null +++ b/website/docs/r/monitor_alert_v2_group_outlier.md @@ -0,0 +1,147 @@ +--- +subcategory: "Sysdig Monitor" +layout: "sysdig" +page_title: "Sysdig: sysdig_monitor_alert_v2_group_outlier" +description: |- + Creates a Sysdig Monitor Group Outlier Alert with AlertV2 API. +--- + +# Resource: sysdig_monitor_alert_v2_group_outlier + +Creates a Sysdig Monitor Group Outlier Alert. Monitor specific segments in a metric to identify entities that deviate from the group. + +-> **Note:** Sysdig Terraform Provider is under rapid development at this point. If you experience any issue or discrepancy while using it, please make sure you have the latest version. If the issue persists, or you have a Feature Request to support an additional set of resources, please open a [new issue](https://github.com/sysdiglabs/terraform-provider-sysdig/issues/new) in the GitHub repository. + +## Example Usage + +```terraform +resource "sysdig_monitor_alert_v2_group_outlier" "sample" { + + name = "cpu usage outliers" + severity = "high" + metric = "sysdig_container_cpu_used_percent" + + algorithm = "MAD" + mad_threshold = 10.1 + mad_tolerance = 5.5 + + group_aggregation = "avg" + group_by = ["kube_pod_name", "container_name"] + time_aggregation = "avg" + + scope { + label = "kube_cluster_name" + operator = "in" + values = ["my_cluster_1", "my_cluster_2"] + } + + scope { + label = "kube_deployment_name" + operator = "equals" + values = ["my_deployment"] + } + + notification_channels { + id = 1234 + renotify_every_minutes = 60 + } + + observation_window_minutes = 15 + +} + +``` + +## Argument Reference + +### Common alert arguments + +These arguments are common to all alerts in Sysdig Monitor. + +* `name` - (Required) The name of the Monitor alert. It must be unique. +* `description` - (Optional) The description of Monitor alert. +* `group` - (Optional) Lowercase string to group alerts in the UI. +* `severity` - (Optional) Severity of the Monitor alert. It must be `high`, `medium`, `low` or `info`. Default: `low`. +* `enabled` - (Optional) Boolean that defines if the alert is enabled or not. Default: `true`. +* `notification_channels` - (Optional) List of notification channel configurations. +* `custom_notification` - (Optional) Allows to define a custom notification title, prepend and append text. +* `link` - (Optional) List of links to add to notifications. + +### `notification_channels` + +By defining this field, the user can choose to which notification channels send the events when the alert fires. + +It is a list of objects with the following fields: +* `id` - (Required) The ID of the notification channel. +* `renotify_every_minutes` - (Optional) the amount of minutes to wait before re sending the notification to this channel. `0` means no renotification enabled. Default: `0`. +* `notify_on_resolve` - (Optional) Wether to send a notification when the alert is resolved. Default: `true`. +* `main_threshold` - (Optional) Whether this notification channel is used for the main threshold of the alert. Default: `true`. +* `warning_threshold` - (Optional) Whether this notification channel is used for the warning threshold of the alert. Default: `false`. + +### `custom_notification` + +By defining this field, the user can modify the title and the body of the message sent when the alert is fired. + +* `subject` - (Optional) Sets the title of the alert. +* `prepend` - (Optional) Text to add before the alert template. +* `append` - (Optional) Text to add after the alert template. + +### `link` + +By defining this field, the user can add link to notifications. + +* `type` - (Required) Type of link. Must be `runbook`, for generic links, or `dashboard`, for internal links to existing dashboards. +* `href` - (Optional) When using `runbook` type, url of the external resource. +* `id` - (Optional) When using `dashboard` type, dashboard id. + +### `capture` + +Enables the creation of a capture file of the syscalls during the event. + +* `filename` - (Required) Defines the name of the capture file. Must have `.scap` suffix. +* `duration_seconds` - (Optional) Time frame of the capture. Default: `15`. +* `storage` - (Optional) Custom bucket where to save the capture. +* `filter` - (Optional) Additional filter to apply to the capture. For example: `proc.name contains nginx`. +* `enabled` - (Optional) Wether to enable captures. Default: `true`. + +### Group Outlier alert arguments + +* `observation_window_minutes` - (Required) Specific time frame in minutes for evaluating potential outliers. The minimum value is ten minutes. +* `scope` - (Optional) Part of the infrastructure where the alert is valid. Defaults to the entire infrastructure. Can be repeated. +* `group_by` - (Required) List of segments to trigger a separate alert on. Example: `["kube_cluster_name", "kube_pod_name"]`. +* `metric` - (Required) Metric the alert will act upon. +* `time_aggregation` - (Required) time aggregation function for data. It can be `avg`, `timeAvg`, `sum`, `min`, `max`. +* `group_aggregation` - (Required) group aggregation function for data. It can be `avg`, `sum`, `min`, `max`. +* `algorithm` - (Required) Algorithm to use to detect outliers. Can be `MAD` (Median Absolute Deviation) or `DBSCAN` (Density-Based Spatial Clustering of Applications with Noise). +* `dbscan_tolerance` - (Optional - Required if `algorithm = DBSCAN`) Proximity range within which an entity should find neighboring time series to be part of a group. Allowed values are between 0.5 and 10. +* `mad_tolerance` - (Optional - Required if `algorithm = MAD`) Tolerance to decide the acceptable values from the median absolute deviation. Allowed values are between 0.5 and 10. +* `mad_threshold` - (Optional - required if `algorithm = MAD`) Percentage of the observation window in which an entity’s reported value must fall outside the configured tolerance to be labeled as an outlier. Allowed values are between 1 and 100. +* `no_data_behaviour` - (Optional) behaviour in case of missing data. Can be `DO_NOTHING`, i.e. ignore, or `TRIGGER`, i.e. notify on main threshold. Default: `DO_NOTHING`. +* `unreported_alert_notifications_retention_seconds` - (Optional) Period after which any alerts triggered for entities (such as containers or hosts) that are no longer reporting data will be automatically marked as 'deactivated'. By default there is no deactivation. + +### `scope` + +* `label` - (Required) Label in prometheus notation to select a part of the infrastructure. +* `operator` - (Required) Operator to match the label. It can be `equals`, `notEquals`, `in`, `notIn`, `contains`, `notContains`, `startsWith`. +* `values` - (Required) List of values to match the scope. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +### Common alert attributes + +In addition to all arguments above, the following attributes are exported, which are common to all the alerts in Sysdig Monitor: + +* `id` - ID of the alert created. +* `version` - Current version of the resource in Sysdig Monitor. +* `team` - Team ID that owns the alert. + + +## Import + +Group Outlier alerts can be imported using the alert ID, e.g. + +``` +$ terraform import sysdig_monitor_alert_v2_group_outlier.example 12345 +```