Skip to content

Commit

Permalink
feat: Adding Webhooks to Index Rate Alert
Browse files Browse the repository at this point in the history
adding the ability to add webhooks for
Index Rate Alerts

Ref:[LOG-9667](https://mezmo.atlassian.net/browse/LOG-9667)
  • Loading branch information
Arani Sen authored and arani-sen committed Jan 11, 2023
1 parent b8b7710 commit 4697645
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 10 deletions.
11 changes: 11 additions & 0 deletions docs/resources/logdna_index_rate_alert.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,18 @@ resource "logdna_index_rate_alert" "config" {
slack = ["https://slack_url/key"]
pagerduty = ["service_key"]
}
webhook_channel {
url = "https:/testurl.com"
method = "POST"
headers = {
header1 = "value1"
}
bodytemplate = jsonencode({
something = "something"
})
}
}
```

## Destroy
Expand Down
12 changes: 11 additions & 1 deletion examples/index_rate_alert.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,14 @@ resource "logdna_index_rate_alert" "config" {
slack = ["https://slack_url/key"]
pagerduty = ["service_key"]
}
}
webhook_channel {
url = "https://something.com"
method = "PUT"
headers = {
field2 = "value2"
}
bodytemplate = `jsonencode({
something = "!something"
})`
}
}
83 changes: 77 additions & 6 deletions logdna/request_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,18 @@ type keyRequest struct {
Name string `json:"name,omitempty"`
}

type indexRateAlertWebhookRequest struct {
URL string `json:"url,omitempty"`
Method string `json:"method,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
BodyTemplate map[string]interface{} `json:"bodyTemplate,omitempty"`
}

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

type indexRateAlertRequest struct {
Expand Down Expand Up @@ -139,7 +147,6 @@ func (doc *indexRateAlertRequest) CreateRequestBody(d *schema.ResourceData) diag
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"),
Expand All @@ -158,7 +165,7 @@ func (doc *indexRateAlertRequest) CreateRequestBody(d *schema.ResourceData) diag
indexRateAlertChannel.Email = listToStrings(channel["email"].([]interface{}))
indexRateAlertChannel.Pagerduty = listToStrings(channel["pagerduty"].([]interface{}))
indexRateAlertChannel.Slack = listToStrings(channel["slack"].([]interface{}))

indexRateAlertChannel.Webhook = *aggregateIndexRateAlertWebhookFromSchema(d, &diags)
doc.Channels = indexRateAlertChannel

return diags
Expand All @@ -185,6 +192,24 @@ func (member *memberPutRequest) CreateRequestBody(d *schema.ResourceData) diag.D
return diags
}

func aggregateIndexRateAlertWebhookFromSchema(
d *schema.ResourceData,
diags *diag.Diagnostics,
) *[]indexRateAlertWebhookRequest {

allWebhookEntries := make([]indexRateAlertWebhookRequest, 0)

allWebhookEntries = append(
allWebhookEntries,
*iterateIndexRateAlertWebhookType(
d.Get("webhook_channel").([]interface{}),
diags,
)...,
)

return &allWebhookEntries
}

func aggregateAllChannelsFromSchema(
d *schema.ResourceData,
diags *diag.Diagnostics,
Expand Down Expand Up @@ -269,6 +294,49 @@ func iterateIntegrationType(
return &channelRequests
}

func iterateIndexRateAlertWebhookType(
listEntries []interface{},
diags *diag.Diagnostics,
) *[]indexRateAlertWebhookRequest {
webhookRequests := []indexRateAlertWebhookRequest{}

for _, entry := range listEntries {
e := entry.(map[string]interface{})
headersMap := make(map[string]string)

for k, v := range e["headers"].(map[string]interface{}) {
headersMap[k] = v.(string)
}

var c interface{}
var bt map[string]interface{}

if bodyTemplate := e["bodytemplate"].(string); bodyTemplate != "" {
// See if the JSON is valid, but don't use the value or it will double encode
err := json.Unmarshal([]byte(bodyTemplate), &bt)

if err != nil {
*diags = append(*diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "bodytemplate is not a valid JSON string",
Detail: err.Error(),
})
}
}

c = indexRateAlertWebhookRequest{
Headers: headersMap,
Method: e["method"].(string),
URL: e["url"].(string),
BodyTemplate: bt,
}

webhookRequests = append(webhookRequests, c.(indexRateAlertWebhookRequest))
}

return &webhookRequests
}

func emailChannelRequest(s map[string]interface{}) channelRequest {
var emails []string
for _, email := range s["emails"].([]interface{}) {
Expand Down Expand Up @@ -317,7 +385,10 @@ func slackChannelRequest(s map[string]interface{}) channelRequest {
return c
}

func webHookChannelRequest(s map[string]interface{}, diags *diag.Diagnostics) channelRequest {
func webHookChannelRequest(
s map[string]interface{},
diags *diag.Diagnostics,
) channelRequest {
headersMap := make(map[string]string)

for k, v := range s["headers"].(map[string]interface{}) {
Expand Down
50 changes: 50 additions & 0 deletions logdna/resource_index_rate_alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"log"
"reflect"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -100,6 +101,9 @@ func resourceIndexRateAlertRead(ctx context.Context, d *schema.ResourceData, m i
integrations["email"] = indexRateAlert.Channels.Email
integrations["pagerduty"] = indexRateAlert.Channels.Pagerduty
integrations["slack"] = indexRateAlert.Channels.Slack
webhooks := mapIndexRateAlertWebhookToSchema(indexRateAlert)

appendError(d.Set("webhook_channel", webhooks), &diags)

channels = append(channels, integrations)

Expand Down Expand Up @@ -214,6 +218,52 @@ func resourceIndexRateAlert() *schema.Resource {
},
},
},
"webhook_channel":{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"url": {
Type: schema.TypeString,
Required: true,
},
"method": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"GET", "POST","PUT","DELETE"}, false),
},
"headers": &schema.Schema{
Type: schema.TypeMap,
Optional:true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Computed: true,
},
"bodytemplate": {
Type: schema.TypeString,
Optional: true,
// This function compares JSON, ignoring whitespace that can occur in a .tf config.
// Without this, `terraform apply` will think values are different from remote to state.
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
var jsonOld, jsonNew interface{}
var err error
err = json.Unmarshal([]byte(old), &jsonOld)
if err != nil {
return false
}
err = json.Unmarshal([]byte(new), &jsonNew)
if err != nil {
return false
}
shouldSuppress := reflect.DeepEqual(jsonNew, jsonOld)
log.Println("[DEBUG] Does view 'bodytemplate' value in state appear the same as remote?", shouldSuppress)
return shouldSuppress
},
},
},
},
},
"enabled": {
Type: schema.TypeBool,
Required: true,
Expand Down
56 changes: 56 additions & 0 deletions logdna/resource_index_rate_alert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,42 @@ func TestIndexRateAlert_ErrorChannels(t *testing.T) {
})
}

func TestIndexRateAlert_ErrorInvalidTokenWebhookBodyTemplate(t *testing.T) {
iraArgs := map[string]string{
"max_lines": `3`,
"max_z_score": `3`,
"threshold_alert": `"separate"`,
"frequency": `"hourly"`,
"enabled": `false`,
}

chArgs := map[string]map[string]string{
"channels": {
"email": `["[email protected]"]`,
},
"webhook_channel": {
"url": `"https://something.com"`,
"method": `"POST"`,
"headers": `{
field2 = "value2"
}`,
"bodytemplate": `jsonencode({
something = "{{maxLines}}"
})`,
},
}

resource.Test(t, resource.TestCase{
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: fmtTestConfigResource("index_rate_alert", "test_config", globalPcArgs, iraArgs, chArgs, nilLst),
ExpectError: regexp.MustCompile("Invalid bodyTemplate: {{maxLines}} is not a valid token"),
},
},
})
}

func TestIndexRateAlert_Basic(t *testing.T) {
iraArgs := map[string]string{
"max_lines": `3`,
Expand All @@ -179,6 +215,16 @@ func TestIndexRateAlert_Basic(t *testing.T) {
"slack": `["https://hooks.slack.com/KEY"]`,
"pagerduty": `["ndt3k75rsw520d8t55dv35decdyt3mkcb3r"]`,
},
"webhook_channel": {
"url" : `"https://something.com"`,
"method": `"POST"`,
"headers" : `{
field2 = "value2"
}`,
"bodytemplate" :`jsonencode({
something = "something"
})`,
},
}

iraUpdArgs := map[string]string{
Expand All @@ -195,6 +241,16 @@ func TestIndexRateAlert_Basic(t *testing.T) {
"slack": `["https://hooks.slack.com/UPDATED_KEY", "https://hooks.slack.com/KEY_2"]`,
"pagerduty": `["new3k75rsw520d8t55dv35decdyt3mkcnew"]`,
},
"webhook_channel": {
"url" : `"https://something.com"`,
"method": `"PUT"`,
"headers" : `{
field2 = "value2"
}`,
"bodytemplate" :`jsonencode({
something = "!something"
})`,
},
}

createdEmails := strings.Split(
Expand Down
29 changes: 26 additions & 3 deletions logdna/response_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,18 @@ type categoryResponse struct {
Id string `json:"id"`
}

type indexRateAlertWebhookResponse struct {
URL string `json:"url,omitempty"`
Method string `json:"method,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
BodyTemplate string `json:"bodyTemplate,omitempty"`
}

type indexRateAlertChannelResponse struct {
Email []string `json:"email,omitempty"`
Pagerduty []string `json:"pagerduty,omitempty"`
Slack []string `json:"slack,omitempty"`
Email []string `json:"email,omitempty"`
Pagerduty []string `json:"pagerduty,omitempty"`
Slack []string `json:"slack,omitempty"`
Webhook []indexRateAlertWebhookResponse `json:"webhook,omitempty"`
}

type indexRateAlertResponse struct {
Expand All @@ -105,6 +113,21 @@ type indexRateAlertResponse struct {
Enabled bool `json:"enabled,omitempty"`
}

func mapIndexRateAlertWebhookToSchema(indexRateAlert indexRateAlertResponse) []interface{} {
webhooks := make([]interface{}, 0)

for _, webhook := range indexRateAlert.Channels.Webhook {
w := make(map[string]interface{})

w["bodytemplate"] = webhook.BodyTemplate
w["headers"] = webhook.Headers
w["method"] = webhook.Method
w["url"] = webhook.URL

webhooks = append(webhooks, w)
}
return webhooks
}
func (view *viewResponse) MapChannelsToSchema() (map[string][]interface{}, diag.Diagnostics) {
channels := view.Channels
channelIntegrations, diags := mapAllChannelsToSchema("view", &channels)
Expand Down

0 comments on commit 4697645

Please sign in to comment.