From d101c1713693213f3320da21d06b36e2276d75d3 Mon Sep 17 00:00:00 2001 From: TwiN Date: Thu, 20 Oct 2022 17:05:01 -0400 Subject: [PATCH] fix(alerting): Resolve Mattermost issue with bad payload when condition has `"` in it --- alerting/provider/mattermost/mattermost.go | 80 ++++++++++++------- .../provider/mattermost/mattermost_test.go | 10 +-- 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/alerting/provider/mattermost/mattermost.go b/alerting/provider/mattermost/mattermost.go index 3ea1d2f8..07a41d3c 100644 --- a/alerting/provider/mattermost/mattermost.go +++ b/alerting/provider/mattermost/mattermost.go @@ -2,6 +2,7 @@ package mattermost import ( "bytes" + "encoding/json" "fmt" "io" "net/http" @@ -68,9 +69,31 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert, return err } +type Body struct { + Text string `json:"text"` + Username string `json:"username"` + IconURL string `json:"icon_url"` + Attachments []Attachment `json:"attachments"` +} + +type Attachment struct { + Title string `json:"title"` + Fallback string `json:"fallback"` + Text string `json:"text"` + Short bool `json:"short"` + Color string `json:"color"` + Fields []Field `json:"fields"` +} + +type Field struct { + Title string `json:"title"` + Value string `json:"value"` + Short bool `json:"short"` +} + // buildRequestBody builds the request body for the provider -func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string { - var message, color string +func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte { + var message, color, results string if resolved { message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold) color = "#36A64F" @@ -78,7 +101,6 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert * message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold) color = "#DD0000" } - var results string for _, conditionResult := range result.ConditionResults { var prefix string if conditionResult.Success { @@ -86,38 +108,34 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert * } else { prefix = ":x:" } - results += fmt.Sprintf("%s - `%s`\\n", prefix, conditionResult.Condition) + results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition) } var description string if alertDescription := alert.GetDescription(); len(alertDescription) > 0 { - description = ":\\n> " + alertDescription + description = ":\n> " + alertDescription } - return fmt.Sprintf(`{ - "text": "", - "username": "gatus", - "icon_url": "https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png", - "attachments": [ - { - "title": ":rescue_worker_helmet: Gatus", - "fallback": "Gatus - %s", - "text": "%s%s", - "short": false, - "color": "%s", - "fields": [ - { - "title": "URL", - "value": "%s", - "short": false - }, - { - "title": "Condition results", - "value": "%s", - "short": false - } - ] - } - ] -}`, message, message, description, color, endpoint.URL, results) + body, _ := json.Marshal(Body{ + Text: "", + Username: "gatus", + IconURL: "https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png", + Attachments: []Attachment{ + { + Title: ":helmet_with_white_cross: Gatus", + Fallback: "Gatus - " + message, + Text: message + description, + Short: false, + Color: color, + Fields: []Field{ + { + Title: "Condition results", + Value: results, + Short: false, + }, + }, + }, + }, + }) + return body } // getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group diff --git a/alerting/provider/mattermost/mattermost_test.go b/alerting/provider/mattermost/mattermost_test.go index 48e43c49..481612e9 100644 --- a/alerting/provider/mattermost/mattermost_test.go +++ b/alerting/provider/mattermost/mattermost_test.go @@ -155,14 +155,14 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { Provider: AlertProvider{}, Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: false, - ExpectedBody: "{\n \"text\": \"\",\n \"username\": \"gatus\",\n \"icon_url\": \"https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png\",\n \"attachments\": [\n {\n \"title\": \":rescue_worker_helmet: Gatus\",\n \"fallback\": \"Gatus - An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row\",\n \"text\": \"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row:\\n> description-1\",\n \"short\": false,\n \"color\": \"#DD0000\",\n \"fields\": [\n {\n \"title\": \"URL\",\n \"value\": \"\",\n \"short\": false\n },\n {\n \"title\": \"Condition results\",\n \"value\": \":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\n \"short\": false\n }\n ]\n }\n ]\n}", + ExpectedBody: "{\"text\":\"\",\"username\":\"gatus\",\"icon_url\":\"https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"fallback\":\"Gatus - An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row\",\"text\":\"An alert for *endpoint-name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", }, { Name: "resolved", Provider: AlertProvider{}, Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3}, Resolved: true, - ExpectedBody: "{\n \"text\": \"\",\n \"username\": \"gatus\",\n \"icon_url\": \"https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png\",\n \"attachments\": [\n {\n \"title\": \":rescue_worker_helmet: Gatus\",\n \"fallback\": \"Gatus - An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row\",\n \"text\": \"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row:\\n> description-2\",\n \"short\": false,\n \"color\": \"#36A64F\",\n \"fields\": [\n {\n \"title\": \"URL\",\n \"value\": \"\",\n \"short\": false\n },\n {\n \"title\": \"Condition results\",\n \"value\": \":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\n \"short\": false\n }\n ]\n }\n ]\n}", + ExpectedBody: "{\"text\":\"\",\"username\":\"gatus\",\"icon_url\":\"https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"fallback\":\"Gatus - An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row\",\"text\":\"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row:\\n\\u003e description-2\",\"short\":false,\"color\":\"#36A64F\",\"fields\":[{\"title\":\"Condition results\",\"value\":\":white_check_mark: - `[CONNECTED] == true`\\n:white_check_mark: - `[STATUS] == 200`\\n\",\"short\":false}]}]}", }, } for _, scenario := range scenarios { @@ -178,11 +178,11 @@ func TestAlertProvider_buildRequestBody(t *testing.T) { }, scenario.Resolved, ) - if body != scenario.ExpectedBody { - t.Errorf("expected %s, got %s", scenario.ExpectedBody, body) + if string(body) != scenario.ExpectedBody { + t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body) } out := make(map[string]interface{}) - if err := json.Unmarshal([]byte(body), &out); err != nil { + if err := json.Unmarshal(body, &out); err != nil { t.Error("expected body to be valid JSON, got error:", err.Error()) } })