Skip to content

Commit

Permalink
feat(index rate alert): add support of index rate alert api
Browse files Browse the repository at this point in the history
Added Index Rate Alert resource
The API allows only PUT and GET endpoints
We use PUT endpoint for Create, Update and Delete resource functions
Delete resource disables index rate alert config

Ref: LOG-13487
  • Loading branch information
seeruyy committed Aug 25, 2022
1 parent d701da9 commit d5d6074
Show file tree
Hide file tree
Showing 11 changed files with 850 additions and 109 deletions.
76 changes: 76 additions & 0 deletions docs/resources/logdna_index_rate_alert.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Resource: `logdna_index_rate_alert`

Manages [LogDNA Index Rate Alert](https://docs.mezmo.com/docs/index-rate-alerts). Configuring alerts based on the index rate or retention and storage rate of your log data helps you track unusual behavior in your systems. For example, if there's a sudden spike in volume, Mezmo's Index Rate Alert feature tells you which applications or sources produced the data spike. It also shows any recently added sources. Index rate alerts can also help managers who are responsible for budgets to analyze and predict storage costs.

To get started, all you need to do is to specify a configuration and one of our currently supported alerts recipients: email, Slack, or PagerDuty.

Be aware that only one index rate alert configuration is allowed per account

## Example - Index Rate Alert

```hcl
provider "logdna" {
servicekey = "xxxxxxxxxxxxxxxxxxxxxxxx"
url = "https://api.logdna.com" # (Optional) specify a LogDNA region
}
resource "logdna_index_rate_alert" "config" {
max_lines = 3
max_z_score = 3
threshold_alert = "separate"
frequency = "hourly"
enabled = true
channels {
email = ["[email protected]"]
slack = ["https://slack_url/key"]
pagerduty = ["service_key"]
}
}
```

## Destroy
There is not a DELETE endpoint supported by the Index Rate Alert API. For this reason, removing the Index Rate Alert Config effectively disables it. (set enabled to false in DB)

## Import

Index Rate Alert can be imported by static ID "config", which can be found using the [Get Index Rate Alert API](https://docs.mezmo.com/log-analysis-api/ref#get-index-rate-alert):

1. Custom HTTP Headers - `servicekey: <SERVICE_KEY>` or `apikey: <SERVICE_KEY>`
```sh
curl --request GET \
--url <API_URL>/v1/config/index-rate \
--header 'Accept: application/json' \
--header 'servicekey: <SERVICE_KEY>'
```
2. Basic Auth - `Authorization: Basic <encodeInBase64(credentials)>`.<br />
Credentials is a string formatted as `<username>:<password>`. Our usage here entails substituting `<SERVICE_KEY>` as the username and leaving the password blank. The colon separator should still be included in the resulting string `<SERVICE_KEY>:`
```sh
curl --request GET \
--url <API_URL>/v1/config/index-rate \
--header 'Accept: application/json' \
--header 'Authorization: Basic <BASE_64_ENCODED_CREDENTIALS>'
```

```sh
terraform import logdna_index_rate_alert.config config
```

Note that only the alert channels supported by this provider will be imported.

## Argument Reference

The following arguments are supported by `logdna_alert`:

- `max_lines`: The number of lines required in order to set off the alert, type _int_
- `max_z_score`: The number of standard deviations above the 30-day average lines in order to set off the alert, type _int_
- `threshold_alert`: Set if you want alerts to be triggered if one or both of the max lines and standard deviation have been triggered or individually, type _string_ ["separate" | "both"]
- `frequency`: Notify recipients once per hour or once per day (starting from the first passing of the threshold) until the index rate declines back below the thresholds, ceasing all alerts., type _string_ ["hourly" | "daily"]
- `enabled`: (Required) Enable an existing configuration, type _boolean_

### channels

`channels` supports the following arguments:

- `email`: **_[]string_** An array of email addresses (strings) to notify
- `slack`: **_[]string_** An array of slack hook urls (strings) to notify
- `pagerduty`: **_[]string_** An array of pagerduty service integration keys (strings) to notify
16 changes: 16 additions & 0 deletions examples/index_rate_alert.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
provider "logdna" {
servicekey = "Your service key goes here"
}

resource "logdna_index_rate_alert" "config" {
max_lines = 3
max_z_score = 3
threshold_alert = "separate"
frequency = "hourly"
enabled = true
channels {
email = ["[email protected]"]
slack = ["https://slack_url/key"]
pagerduty = ["service_key"]
}
}
10 changes: 5 additions & 5 deletions logdna/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ var rsDefaults = map[string]map[string]string{
},
}
var chnlDefaults = map[string]map[string]string{
"email": {
"email_channel": {
"emails": `["[email protected]"]`,
"immediate": `"false"`,
"operator": `"absence"`,
Expand All @@ -48,23 +48,23 @@ var chnlDefaults = map[string]map[string]string{
"triggerinterval": `"15m"`,
"triggerlimit": `15`,
},
"pagerduty": {
"pagerduty_channel": {
"immediate": `"false"`,
"operator": `"presence"`,
"key": `"Your PagerDuty API key goes here"`,
"terminal": `"true"`,
"triggerinterval": `"15m"`,
"triggerlimit": `15`,
},
"slack": {
"slack_channel": {
"immediate": `"false"`,
"operator": `"absence"`,
"terminal": `"true"`,
"triggerinterval": `"30m"`,
"triggerlimit": `15`,
"url": `"https://hooks.slack.com/services/identifier/secret"`,
},
"webhook": {
"webhook_channel": {
"headers": "{\n" +
"\t\t\thello = \"test3\"\n" +
"\t\t\ttest = \"test2\"\n" +
Expand Down Expand Up @@ -124,7 +124,7 @@ func fmtResourceBlock(objTyp, rsName string, rsArgs map[string]string, chArgs ma

rgxDgt := regexp.MustCompile(`\d+`)
for chName, chArgs := range chArgs {
fmt.Fprintf(&rsCfg, "\t%s_channel {\n", rgxDgt.ReplaceAllString(chName, ""))
fmt.Fprintf(&rsCfg, "\t%s {\n", rgxDgt.ReplaceAllString(chName, ""))
fmt.Fprint(&rsCfg, fmtBlockArgs(2, chArgs))
fmt.Fprintf(&rsCfg, "\t}\n")
}
Expand Down
24 changes: 12 additions & 12 deletions logdna/data_source_alert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,26 @@ data "logdna_alert" "remote" {

func TestDataAlert_BulkChannels(t *testing.T) {
emArgs := map[string]map[string]string{
"email": cloneDefaults(chnlDefaults["email"]),
"email1": cloneDefaults(chnlDefaults["email"]),
"email_channel": cloneDefaults(chnlDefaults["email_channel"]),
"email1_channel": cloneDefaults(chnlDefaults["email_channel"]),
}
emsCfg := fmtTestConfigResource("alert", "test", globalPcArgs, alertDefaults, emArgs, nilLst)

pdArgs := map[string]map[string]string{
"pagerduty": cloneDefaults(chnlDefaults["pagerduty"]),
"pagerduty1": cloneDefaults(chnlDefaults["pagerduty"]),
"pagerduty_channel": cloneDefaults(chnlDefaults["pagerduty_channel"]),
"pagerduty1_channel": cloneDefaults(chnlDefaults["pagerduty_channel"]),
}
pdsCfg := fmtTestConfigResource("alert", "test", globalPcArgs, alertDefaults, pdArgs, nilLst)

slArgs := map[string]map[string]string{
"slack": cloneDefaults(chnlDefaults["slack"]),
"slack1": cloneDefaults(chnlDefaults["slack"]),
"slack_channel": cloneDefaults(chnlDefaults["slack_channel"]),
"slack1_channel": cloneDefaults(chnlDefaults["slack_channel"]),
}
slsCfg := fmtTestConfigResource("alert", "test", globalPcArgs, alertDefaults, slArgs, nilLst)

wbArgs := map[string]map[string]string{
"webhook": cloneDefaults(chnlDefaults["webhook"]),
"webhook1": cloneDefaults(chnlDefaults["webhook"]),
"webhook_channel": cloneDefaults(chnlDefaults["webhook_channel"]),
"webhook1_channel": cloneDefaults(chnlDefaults["webhook_channel"]),
}
wbsCfg := fmtTestConfigResource("alert", "test", globalPcArgs, alertDefaults, wbArgs, nilLst)

Expand Down Expand Up @@ -99,10 +99,10 @@ func TestDataAlert_BulkChannels(t *testing.T) {

func TestDataSourceAlert_MultipleChannels(t *testing.T) {
chArgs := map[string]map[string]string{
"email": cloneDefaults(chnlDefaults["email"]),
"pagerduty": cloneDefaults(chnlDefaults["pagerduty"]),
"slack": cloneDefaults(chnlDefaults["slack"]),
"webhook": cloneDefaults(chnlDefaults["webhook"]),
"email_channel": cloneDefaults(chnlDefaults["email_channel"]),
"pagerduty_channel": cloneDefaults(chnlDefaults["pagerduty_channel"]),
"slack_channel": cloneDefaults(chnlDefaults["slack_channel"]),
"webhook_channel": cloneDefaults(chnlDefaults["webhook_channel"]),
}
fmtCfg := fmt.Sprintf("%s\n%s", fmtTestConfigResource("alert", "test", globalPcArgs, alertDefaults, chArgs, nilLst), ds)

Expand Down
1 change: 1 addition & 0 deletions logdna/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func Provider() *schema.Provider {
"logdna_ingestion_exclusion": resourceIngestionExclusion(),
"logdna_archive": resourceArchiveConfig(),
"logdna_key": resourceKey(),
"logdna_index_rate_alert": resourceIndexRateAlert(),
},
ConfigureFunc: providerConfigure,
}
Expand Down
46 changes: 46 additions & 0 deletions logdna/request_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package logdna
import (
"encoding/json"
"fmt"
"errors"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -54,6 +55,21 @@ type keyRequest struct {
Name string `json:"name,omitempty"`
}

type indexRateAlertChannelRequest struct {
Email []string `json:"email,omitempty"`
Pagerduty []string `json:"pagerduty,omitempty"`
Slack []string `json:"slack,omitempty"`
}

type indexRateAlertRequest struct {
MaxLines int `json:"max_lines,omitempty"`
MaxZScore int `json:"max_z_score,omitempty"`
ThresholdAlert string `json:"threshold_alert,omitempty"`
Frequency string `json:"frequency,omitempty"`
Channels indexRateAlertChannelRequest `json:"channels,omitempty"`
Enabled bool `json:"enabled,omitempty"`
}

func (view *viewRequest) CreateRequestBody(d *schema.ResourceData) diag.Diagnostics {
// This function pulls from the schema in preparation to JSON marshal
var diags diag.Diagnostics
Expand Down Expand Up @@ -107,6 +123,36 @@ func (key *keyRequest) CreateRequestBody(d *schema.ResourceData) diag.Diagnostic
return diags
}

func (doc *indexRateAlertRequest) CreateRequestBody(d *schema.ResourceData) diag.Diagnostics {
// This function pulls from the schema in preparation to JSON marshal
var diags diag.Diagnostics

var channels = d.Get("channels").([]interface{})

if len(channels) > 1 {
return diag.FromErr(
errors.New("Index rate alert resource supports only one channels object"),
)
}

doc.MaxLines = d.Get("max_lines").(int)
doc.MaxZScore = d.Get("max_z_score").(int)
doc.Enabled = d.Get("enabled").(bool)
doc.ThresholdAlert = d.Get("threshold_alert").(string)
doc.Frequency = d.Get("frequency").(string)

var indexRateAlertChannel indexRateAlertChannelRequest
var channel = channels[0].(map[string]interface{})

indexRateAlertChannel.Email = listToStrings(channel["email"].([]interface{}))
indexRateAlertChannel.Pagerduty = listToStrings(channel["pagerduty"].([]interface{}))
indexRateAlertChannel.Slack = listToStrings(channel["slack"].([]interface{}))

doc.Channels = indexRateAlertChannel

return diags
}

func aggregateAllChannelsFromSchema(
d *schema.ResourceData,
diags *diag.Diagnostics,
Expand Down
Loading

0 comments on commit d5d6074

Please sign in to comment.