internal/clients/kibana/alerting.go (273 lines of code) (raw):

package kibana import ( "context" "fmt" "net/http" "github.com/elastic/terraform-provider-elasticstack/generated/alerting" "github.com/elastic/terraform-provider-elasticstack/internal/clients" "github.com/elastic/terraform-provider-elasticstack/internal/models" "github.com/elastic/terraform-provider-elasticstack/internal/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" ) func ruleResponseToModel(spaceID string, res *alerting.RuleResponseProperties) *models.AlertingRule { if res == nil { return nil } actions := []models.AlertingRuleAction{} for _, action := range res.Actions { a := models.AlertingRuleAction{ Group: action.Group, ID: action.Id, Params: action.Params, } if !alerting.IsNil(action.Frequency) { frequency := unwrapOptionalField(action.Frequency) a.Frequency = &models.ActionFrequency{ Summary: frequency.Summary, NotifyWhen: (string)(frequency.NotifyWhen), Throttle: frequency.Throttle.Get(), } } if !alerting.IsNil(action.AlertsFilter) { filter := unwrapOptionalField(action.AlertsFilter) a.AlertsFilter = &models.ActionAlertsFilter{} if filter.Query != nil { a.AlertsFilter.Kql = filter.Query.Kql } if filter.Timeframe != nil { timeframe := unwrapOptionalField(filter.Timeframe) a.AlertsFilter.Timeframe = &models.AlertsFilterTimeframe{ Days: timeframe.Days, Timezone: *timeframe.Timezone, HoursStart: *timeframe.Hours.Start, HoursEnd: *timeframe.Hours.End, } } } actions = append(actions, a) } var alertDelay *float32 alertDelayActive := unwrapOptionalField(res.AlertDelay).Active if alerting.IsNil(res.AlertDelay) { alertDelay = nil } else { alertDelay = &alertDelayActive } return &models.AlertingRule{ RuleID: res.Id, SpaceID: spaceID, Name: res.Name, Consumer: res.Consumer, // DEPRECATED NotifyWhen: res.NotifyWhen.Get(), Params: res.Params, RuleTypeID: res.RuleTypeId, Schedule: models.AlertingRuleSchedule{ Interval: unwrapOptionalField(res.Schedule.Interval), }, Enabled: &res.Enabled, Tags: res.Tags, Throttle: res.Throttle.Get(), ScheduledTaskID: res.ScheduledTaskId, ExecutionStatus: models.AlertingRuleExecutionStatus{ LastExecutionDate: res.ExecutionStatus.LastExecutionDate, Status: res.ExecutionStatus.Status, }, Actions: actions, AlertDelay: alertDelay, } } // Maps the rule actions to the struct required by the request model (ActionsInner) func ruleActionsToActionsInner(ruleActions []models.AlertingRuleAction) []alerting.ActionsInner { actions := []alerting.ActionsInner{} for index := range ruleActions { action := ruleActions[index] actionToAppend := alerting.ActionsInner{ Group: action.Group, Id: action.ID, Params: action.Params, } if !alerting.IsNil(action.Frequency) { frequency := alerting.ActionsInnerFrequency{ Summary: action.Frequency.Summary, NotifyWhen: (alerting.NotifyWhen)(action.Frequency.NotifyWhen), } if action.Frequency.Throttle != nil { frequency.Throttle = *alerting.NewNullableString(action.Frequency.Throttle) } actionToAppend.Frequency = &frequency } if !alerting.IsNil(action.AlertsFilter) { filter := alerting.ActionsInnerAlertsFilter{} if action.AlertsFilter.Kql != nil { filter.Query = &alerting.ActionsInnerAlertsFilterQuery{ Kql: action.AlertsFilter.Kql, Filters: []alerting.Filter{}, } } if action.AlertsFilter.Timeframe != nil { timeframe := action.AlertsFilter.Timeframe 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 } type ApiClient interface { GetAlertingClient() (alerting.AlertingAPI, error) SetAlertingAuthContext(context.Context) context.Context } func CreateAlertingRule(ctx context.Context, apiClient ApiClient, rule models.AlertingRule) (*models.AlertingRule, diag.Diagnostics) { client, err := apiClient.GetAlertingClient() if err != nil { return nil, diag.FromErr(err) } ctxWithAuth := apiClient.SetAlertingAuthContext(ctx) var alertDelay *alerting.AlertDelay if alerting.IsNil(rule.AlertDelay) { alertDelay = nil } else { alertDelay = &alerting.AlertDelay{ Active: *rule.AlertDelay, } } reqModel := alerting.CreateRuleRequest{ Consumer: rule.Consumer, Actions: ruleActionsToActionsInner(rule.Actions), Enabled: rule.Enabled, Name: rule.Name, NotifyWhen: (*alerting.NotifyWhen)(rule.NotifyWhen), Params: rule.Params, RuleTypeId: rule.RuleTypeID, Schedule: alerting.Schedule{ Interval: &rule.Schedule.Interval, }, Tags: rule.Tags, Throttle: *alerting.NewNullableString(rule.Throttle), AlertDelay: alertDelay, } req := client.CreateRuleId(ctxWithAuth, rule.SpaceID, rule.RuleID).KbnXsrf("true").CreateRuleRequest(reqModel) ruleRes, res, err := req.Execute() if err != nil && res == nil { return nil, diag.FromErr(err) } // TODO: Remove this manual check once OpenAPI spec is updated: https://github.com/elastic/kibana/issues/183223 if res.StatusCode == http.StatusConflict { return nil, diag.Errorf("Status code [%d], Saved object [%s/%s] conflict (Rule ID already exists in this Space)", res.StatusCode, rule.SpaceID, rule.RuleID) } defer res.Body.Close() diags := utils.CheckHttpError(res, "Unable to create alerting rule") if diags.HasError() { return nil, diags } if ruleRes == nil { return nil, diag.Diagnostics{diag.Diagnostic{ Severity: diag.Error, Summary: "Create rule returned an empty response", Detail: fmt.Sprintf("Create rule returned an empty response with HTTP status code [%d].", res.StatusCode), }} } rule.RuleID = ruleRes.Id return ruleResponseToModel(rule.SpaceID, ruleRes), nil } func UpdateAlertingRule(ctx context.Context, apiClient ApiClient, rule models.AlertingRule) (*models.AlertingRule, diag.Diagnostics) { client, err := apiClient.GetAlertingClient() if err != nil { return nil, diag.FromErr(err) } ctxWithAuth := apiClient.SetAlertingAuthContext(ctx) var alertDelay *alerting.AlertDelay if alerting.IsNil(rule.AlertDelay) { alertDelay = nil } else { alertDelay = &alerting.AlertDelay{ Active: *rule.AlertDelay, } } reqModel := alerting.UpdateRuleRequest{ Actions: ruleActionsToActionsInner((rule.Actions)), Name: rule.Name, NotifyWhen: (*alerting.NotifyWhen)(rule.NotifyWhen), Params: rule.Params, Schedule: alerting.Schedule{ Interval: &rule.Schedule.Interval, }, Tags: rule.Tags, Throttle: *alerting.NewNullableString(rule.Throttle), AlertDelay: alertDelay, } req := client.UpdateRule(ctxWithAuth, rule.RuleID, rule.SpaceID).KbnXsrf("true").UpdateRuleRequest(reqModel) ruleRes, res, err := req.Execute() if err != nil && res == nil { return nil, diag.FromErr(err) } defer res.Body.Close() if diags := utils.CheckHttpError(res, "Unable to update alerting rule"); diags.HasError() { return nil, diags } if ruleRes == nil { return nil, diag.Diagnostics{diag.Diagnostic{ Severity: diag.Error, Summary: "Update rule returned an empty response", Detail: fmt.Sprintf("Update rule returned an empty response with HTTP status code [%d].", res.StatusCode), }} } rule.RuleID = ruleRes.Id shouldBeEnabled := rule.Enabled != nil && *rule.Enabled if shouldBeEnabled && !ruleRes.Enabled { res, err := client.EnableRule(ctxWithAuth, rule.RuleID, rule.SpaceID).KbnXsrf("true").Execute() if err != nil && res == nil { return nil, diag.FromErr(err) } if diags := utils.CheckHttpError(res, "Unable to enable alerting rule"); diags.HasError() { return nil, diag.FromErr(err) } } if !shouldBeEnabled && ruleRes.Enabled { res, err := client.DisableRule(ctxWithAuth, rule.RuleID, rule.SpaceID).KbnXsrf("true").Execute() if err != nil && res == nil { return nil, diag.FromErr(err) } if diags := utils.CheckHttpError(res, "Unable to disable alerting rule"); diags.HasError() { return nil, diag.FromErr(err) } } return ruleResponseToModel(rule.SpaceID, ruleRes), diag.Diagnostics{} } func GetAlertingRule(ctx context.Context, apiClient *clients.ApiClient, id, spaceID string) (*models.AlertingRule, diag.Diagnostics) { client, err := apiClient.GetAlertingClient() if err != nil { return nil, diag.FromErr(err) } ctxWithAuth := apiClient.SetAlertingAuthContext(ctx) req := client.GetRule(ctxWithAuth, id, spaceID) ruleRes, res, err := req.Execute() if err != nil && res == nil { return nil, diag.FromErr(err) } defer res.Body.Close() if res.StatusCode == http.StatusNotFound { return nil, nil } return ruleResponseToModel(spaceID, ruleRes), utils.CheckHttpError(res, "Unable to get alerting rule") } func DeleteAlertingRule(ctx context.Context, apiClient *clients.ApiClient, ruleId string, spaceId string) diag.Diagnostics { client, err := apiClient.GetAlertingClient() if err != nil { return diag.FromErr(err) } ctxWithAuth := apiClient.SetAlertingAuthContext(ctx) req := client.DeleteRule(ctxWithAuth, ruleId, spaceId).KbnXsrf("true") res, err := req.Execute() if err != nil && res == nil { return diag.FromErr(err) } defer res.Body.Close() return utils.CheckHttpError(res, "Unable to delete alerting rule") }