diff --git a/sysdig/internal/client/v2/client.go b/sysdig/internal/client/v2/client.go index 48ad48bf..0c441cd6 100644 --- a/sysdig/internal/client/v2/client.go +++ b/sysdig/internal/client/v2/client.go @@ -51,6 +51,7 @@ type MonitorCommon interface { AlertInterface AlertV2Interface DashboardInterface + SilenceRuleInterface } type SecureCommon interface { diff --git a/sysdig/internal/client/v2/model.go b/sysdig/internal/client/v2/model.go index be49c74e..cb28720a 100644 --- a/sysdig/internal/client/v2/model.go +++ b/sysdig/internal/client/v2/model.go @@ -714,3 +714,16 @@ type IdentityContext struct { ServiceAccountID int `json:"serviceAccountId"` ServiceAccountName string `json:"serviceAccountName"` } + +type SilenceRule struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` + StartTs int64 `json:"startTs"` + DurationInSec int `json:"durationInSec"` + Scope string `json:"scope,omitempty"` + AlertIds []int `json:"alertIds,omitempty"` + NotificationChannelIds []int `json:"notificationChannelIds,omitempty"` + + Version int `json:"version,omitempty"` + ID int `json:"id,omitempty"` +} diff --git a/sysdig/internal/client/v2/silence_rule.go b/sysdig/internal/client/v2/silence_rule.go new file mode 100644 index 00000000..51f25932 --- /dev/null +++ b/sysdig/internal/client/v2/silence_rule.go @@ -0,0 +1,105 @@ +package v2 + +import ( + "context" + "errors" + "fmt" + "net/http" +) + +const ( + silenceRulesPath = "%s/api/v1/silencingRules" + silenceRulePath = "%s/api/v1/silencingRules/%d" +) + +var SilenceRuleNotFound = errors.New("silence rule not found") + +type SilenceRuleInterface interface { + Base + GetSilenceRule(ctx context.Context, id int) (SilenceRule, error) + CreateSilenceRule(ctx context.Context, silenceRule SilenceRule) (SilenceRule, error) + UpdateSilenceRule(ctx context.Context, silenceRule SilenceRule) (SilenceRule, error) + DeleteSilenceRule(ctx context.Context, id int) error +} + +func (client *Client) GetSilenceRule(ctx context.Context, id int) (SilenceRule, error) { + response, err := client.requester.Request(ctx, http.MethodGet, client.getSilenceRuleURL(id), nil) + if err != nil { + return SilenceRule{}, err + } + defer response.Body.Close() + + if response.StatusCode == http.StatusNotFound { + return SilenceRule{}, SilenceRuleNotFound + } + if response.StatusCode != http.StatusOK { + return SilenceRule{}, client.ErrorFromResponse(response) + } + + silenceRule, err := Unmarshal[SilenceRule](response.Body) + if err != nil { + return SilenceRule{}, err + } + + return silenceRule, nil +} + +func (client *Client) CreateSilenceRule(ctx context.Context, silenceRule SilenceRule) (SilenceRule, error) { + payload, err := Marshal(silenceRule) + if err != nil { + return SilenceRule{}, err + } + + response, err := client.requester.Request(ctx, http.MethodPost, client.getSilenceRulesURL(), payload) + if err != nil { + return SilenceRule{}, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK && response.StatusCode != http.StatusCreated { + return SilenceRule{}, client.ErrorFromResponse(response) + } + + return Unmarshal[SilenceRule](response.Body) +} + +func (client *Client) UpdateSilenceRule(ctx context.Context, silenceRule SilenceRule) (SilenceRule, error) { + payload, err := Marshal(silenceRule) + if err != nil { + return SilenceRule{}, err + } + + response, err := client.requester.Request(ctx, http.MethodPut, client.getSilenceRuleURL(silenceRule.ID), payload) + if err != nil { + return SilenceRule{}, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return SilenceRule{}, client.ErrorFromResponse(response) + } + + return Unmarshal[SilenceRule](response.Body) +} + +func (client *Client) DeleteSilenceRule(ctx context.Context, id int) error { + response, err := client.requester.Request(ctx, http.MethodDelete, client.getSilenceRuleURL(id), nil) + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNotFound { + return client.ErrorFromResponse(response) + } + + return nil +} + +func (client *Client) getSilenceRulesURL() string { + return fmt.Sprintf(silenceRulesPath, client.config.url) +} + +func (client *Client) getSilenceRuleURL(id int) string { + return fmt.Sprintf(silenceRulePath, client.config.url, id) +} diff --git a/sysdig/provider.go b/sysdig/provider.go index b7fb68d4..0003d5a9 100644 --- a/sysdig/provider.go +++ b/sysdig/provider.go @@ -134,6 +134,7 @@ func Provider() *schema.Provider { "sysdig_secure_scanning_policy": resourceSysdigSecureScanningPolicy(), "sysdig_secure_scanning_policy_assignment": resourceSysdigSecureScanningPolicyAssignment(), + "sysdig_monitor_silence_rule": resourceSysdigMonitorSilenceRule(), "sysdig_monitor_alert_downtime": resourceSysdigMonitorAlertDowntime(), "sysdig_monitor_alert_metric": resourceSysdigMonitorAlertMetric(), "sysdig_monitor_alert_event": resourceSysdigMonitorAlertEvent(), diff --git a/sysdig/resource_sysdig_monitor_silence_rule.go b/sysdig/resource_sysdig_monitor_silence_rule.go new file mode 100644 index 00000000..4d993ca3 --- /dev/null +++ b/sysdig/resource_sysdig_monitor_silence_rule.go @@ -0,0 +1,235 @@ +package sysdig + +import ( + "context" + "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 resourceSysdigMonitorSilenceRule() *schema.Resource { + timeout := 5 * time.Minute + + return &schema.Resource{ + CreateContext: resourceSysdigMonitorSilenceRuleCreate, + UpdateContext: resourceSysdigMonitorSilenceRuleUpdate, + ReadContext: resourceSysdigMonitorSilenceRuleRead, + DeleteContext: resourceSysdigMonitorSilenceRuleDelete, + 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: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "start_ts": { + Type: schema.TypeString, + Required: true, + }, + "duration_seconds": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(60), + }, + "alert_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + Optional: true, + }, + "scope": { + Type: schema.TypeString, + Optional: true, + }, + "notification_channel_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + Optional: true, + }, + "version": { + Type: schema.TypeInt, + Computed: true, + }, + }, + } +} + +func getMonitorSilenceRuleClient(c SysdigClients) (v2.SilenceRuleInterface, error) { + var client v2.SilenceRuleInterface + var err error + switch c.GetClientType() { + case IBMMonitor: + client, err = c.ibmMonitorClient() + if err != nil { + return nil, err + } + default: + client, err = c.sysdigMonitorClientV2() + if err != nil { + return nil, err + } + } + return client, nil +} + +func resourceSysdigMonitorSilenceRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, err := getMonitorSilenceRuleClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + silenceRule, err := monitorSilenceRuleFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + silenceRule, err = client.CreateSilenceRule(ctx, silenceRule) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(strconv.Itoa(silenceRule.ID)) + + return resourceSysdigMonitorSilenceRuleRead(ctx, d, meta) +} + +func resourceSysdigMonitorSilenceRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, err := getMonitorSilenceRuleClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + silenceRule, err := client.GetSilenceRule(ctx, id) + + if err != nil { + if err == v2.SilenceRuleNotFound { + d.SetId("") + return nil + } + return diag.FromErr(err) + } + + // suppress diff of "enabled" field if the silence interval is over: it will always be false from the api + // any update of an ended silence rule results in a 422 Unprocessable Entity error from the api + silenceRuleEnd := time.Unix(silenceRule.StartTs/1000+int64(silenceRule.DurationInSec), 0) + if time.Now().After(silenceRuleEnd) { + silenceRule.Enabled = d.Get("enabled").(bool) + } + + err = monitorSilenceRuleToResourceData(silenceRule, d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigMonitorSilenceRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, err := getMonitorSilenceRuleClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + silenceRule, err := monitorSilenceRuleFromResourceData(d) + if err != nil { + return diag.FromErr(err) + } + + silenceRule.Version = d.Get("version").(int) + silenceRule.ID, err = strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + _, err = client.UpdateSilenceRule(ctx, silenceRule) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func resourceSysdigMonitorSilenceRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client, err := getMonitorSilenceRuleClient(meta.(SysdigClients)) + if err != nil { + return diag.FromErr(err) + } + + id, err := strconv.Atoi(d.Id()) + if err != nil { + return diag.FromErr(err) + } + + err = client.DeleteSilenceRule(ctx, id) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func monitorSilenceRuleFromResourceData(d *schema.ResourceData) (v2.SilenceRule, error) { + silenceRule := v2.SilenceRule{} + + silenceRule.Name = d.Get("name").(string) + silenceRule.Enabled = d.Get("enabled").(bool) + startTs, err := strconv.ParseInt(d.Get("start_ts").(string), 10, 64) + if err != nil { + return silenceRule, err + } + silenceRule.StartTs = startTs + silenceRule.DurationInSec = d.Get("duration_seconds").(int) + silenceRule.Scope = d.Get("scope").(string) + alertIds := d.Get("alert_ids").(*schema.Set) + for _, rawAlertId := range alertIds.List() { + if alertId, ok := rawAlertId.(int); ok { + silenceRule.AlertIds = append(silenceRule.AlertIds, alertId) + } + } + notificationChannelIds := d.Get("notification_channel_ids").(*schema.Set) + for _, rawNotificationChannelId := range notificationChannelIds.List() { + if notificationChannelId, ok := rawNotificationChannelId.(int); ok { + silenceRule.NotificationChannelIds = append(silenceRule.NotificationChannelIds, notificationChannelId) + } + } + return silenceRule, nil +} + +func monitorSilenceRuleToResourceData(silenceRule v2.SilenceRule, d *schema.ResourceData) (err error) { + _ = d.Set("name", silenceRule.Name) + _ = d.Set("enabled", silenceRule.Enabled) + _ = d.Set("start_ts", strconv.FormatInt(silenceRule.StartTs, 10)) + _ = d.Set("duration_seconds", silenceRule.DurationInSec) + _ = d.Set("alert_ids", silenceRule.AlertIds) + _ = d.Set("scope", silenceRule.Scope) + _ = d.Set("notification_channel_ids", silenceRule.NotificationChannelIds) + _ = d.Set("version", silenceRule.Version) + return nil +} diff --git a/sysdig/resource_sysdig_monitor_silence_rule_test.go b/sysdig/resource_sysdig_monitor_silence_rule_test.go new file mode 100644 index 00000000..84f25d55 --- /dev/null +++ b/sysdig/resource_sysdig_monitor_silence_rule_test.go @@ -0,0 +1,137 @@ +//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 TestAccMonitorSilenceRule(t *testing.T) { + rText := func() string { return acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: sysdigOrIBMMonitorPreCheck(t), + ProviderFactories: map[string]func() (*schema.Provider, error){ + "sysdig": func() (*schema.Provider, error) { + return sysdig.Provider(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: monitorSilenceRuleWithName(rText()), + }, + { + ResourceName: "sysdig_monitor_silence_rule.sample1", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: monitorSilenceRuleWithAlertIds(rText()), + }, + { + ResourceName: "sysdig_monitor_silence_rule.sample2", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: monitorSilenceRuleWithAlertIdsAndScope(rText()), + }, + { + ResourceName: "sysdig_monitor_silence_rule.sample3", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: monitorSilenceRuleWithNotificationChannels(rText()), + }, + { + ResourceName: "sysdig_monitor_silence_rule.sample4", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func monitorSilenceRuleWithName(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_silence_rule" "sample1" { + name = "Example Silence Rule %s" + enabled = false + start_ts = 1691168134153 + duration_seconds = 3600 + scope = "container.name in (\"test\")" +}`, name) +} + +func monitorSilenceRuleWithAlertIds(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_promql" "sample1" { + name = "TERRAFORM TEST - PROMQL %s 1" + promql = "up" + trigger_after_minutes = 1 + enabled = false +} +resource "sysdig_monitor_alert_promql" "sample2" { + name = "TERRAFORM TEST - PROMQL %s 2" + promql = "up" + trigger_after_minutes = 1 + enabled = false +} +resource "sysdig_monitor_silence_rule" "sample2" { + name = "Example Silence Rule %s" + enabled = false + start_ts = 1691168134153 + duration_seconds = 3600 + alert_ids = [ sysdig_monitor_alert_promql.sample1.id, sysdig_monitor_alert_promql.sample2.id ] +}`, name, name, name) +} + +func monitorSilenceRuleWithAlertIdsAndScope(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_alert_promql" "sample3" { + name = "TERRAFORM TEST - PROMQL %s 3" + promql = "up" + trigger_after_minutes = 1 + enabled = false +} +resource "sysdig_monitor_alert_promql" "sample4" { + name = "TERRAFORM TEST - PROMQL %s 4" + promql = "up" + trigger_after_minutes = 1 + enabled = false +} +resource "sysdig_monitor_silence_rule" "sample3" { + name = "Example Silence Rule %s" + enabled = false + start_ts = 1691168134153 + duration_seconds = 3600 + scope = "container.name in (\"test\")" + alert_ids = [ sysdig_monitor_alert_promql.sample3.id, sysdig_monitor_alert_promql.sample4.id ] +}`, name, name, name) +} + +func monitorSilenceRuleWithNotificationChannels(name string) string { + return fmt.Sprintf(` +resource "sysdig_monitor_notification_channel_webhook" "sample-webhook" { + name = "Example Channel %s - Webhook" + enabled = false + url = "https://example.com/" + send_test_notification = false +} +resource "sysdig_monitor_silence_rule" "sample4" { + name = "Example Silence Rule %s" + enabled = false + start_ts = 1691168134153 + duration_seconds = 3600 + scope = "container.name in (\"test\")" + notification_channel_ids = [sysdig_monitor_notification_channel_webhook.sample-webhook.id] +}`, name, name) +} diff --git a/website/docs/index.md b/website/docs/index.md index 4df33eab..b9b26bec 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -236,6 +236,7 @@ When IBM Workload Protection resources are to be created, this authentication mu > - `sysdig_monitor_notification_channel_custom_webhook` > - `sysdig_monitor_notification_channel_ibm_function` > - `sysdig_monitor_notification_channel_ibm_event_notification` +> - `sysdig_monitor_silence_rule` > - `sysdig_monitor_alert_downtime` > - `sysdig_monitor_alert_event` > - `sysdig_monitor_alert_metric` diff --git a/website/docs/r/monitor_silence_rule.md b/website/docs/r/monitor_silence_rule.md new file mode 100644 index 00000000..33e84aa9 --- /dev/null +++ b/website/docs/r/monitor_silence_rule.md @@ -0,0 +1,65 @@ +--- +subcategory: "Sysdig Monitor" +layout: "sysdig" +page_title: "Sysdig: sysdig_monitor_silence_rule" +description: |- + Creates a Sysdig Monitor Silence Rule. +--- + +# Resource: sysdig_monitor_silence_rule + +Creates a Sysdig Monitor Silence Rule. + +-> **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 "time_static" "start_ts" { + rfc3339 = "2023-07-08T07:00:00Z" +} + +resource "sysdig_monitor_silence_rule" "sample" { + name = "Example Silence Rule" + enabled = true + start_ts = time_static.start_ts.unix * 1000 + duration_seconds = 60 * 60 * 24 + scope = "cloudProvider.region != \"us-east-1\" and not host.hostName contains \"testhost\" and kubernetes.job.name starts with \"prod\" and kubernetes.daemonSet.name in (\"ds1\", \"ds2\")" + alert_ids = [1234, 1235] + notification_channel_ids = [111, 222] +} +``` + +## Argument Reference + +Ended Silence Rules cannot be updated. + +* `name` - (Required) The name of the Silence Rule. + +* `enabled` - (Optional) Whether to enable the Silence Rule. Default: `true`. + +* `start_ts` - (Required) Unix timestamp, in milliseconds, when the Silence Rule starts. + +* `duration_seconds` - (Required) Duration of the Silence Rule, in seconds. + +* `scope` - (Optional) Part of the infrastructure the Silence Rule will be applied to. At least one of `scope` or `alert_ids` must be defined. + +* `alert_ids` - (Optional) List of alerts the Silence Rule will be applied to. At least one of `scope` or `alert_ids` must be defined. + +* `notification_channel_ids` - (Optional) List of notification channels that will be used to notify when the Silence Rule starts and end. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - (Computed) The ID of the Silence Rule. + +* `version` - (Computed) The current version of the Silence Rule. + +## Import + +Silence Rules for Monitor can be imported using the ID, e.g. + +``` +$ terraform import sysdig_monitor_silence_rule.example 12345 +```