mirror of
https://github.com/TwiN/gatus.git
synced 2025-02-16 10:20:00 +01:00
fix(alerting): Support alerts with no conditions for external endpoints (#729)
This commit is contained in:
parent
a4bc3c4dfe
commit
241956b28c
@ -50,7 +50,6 @@ func (provider *AlertProvider) IsValid() bool {
|
|||||||
registeredGroups[override.Group] = true
|
registeredGroups[override.Group] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if both AccessKeyID and SecretAccessKey are specified, we'll use these to authenticate,
|
// if both AccessKeyID and SecretAccessKey are specified, we'll use these to authenticate,
|
||||||
// otherwise if neither are specified, then we'll fall back on IAM authentication.
|
// otherwise if neither are specified, then we'll fall back on IAM authentication.
|
||||||
return len(provider.From) > 0 && len(provider.To) > 0 &&
|
return len(provider.From) > 0 && len(provider.To) > 0 &&
|
||||||
@ -112,7 +111,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
|||||||
|
|
||||||
// buildMessageSubjectAndBody builds the message subject and body
|
// buildMessageSubjectAndBody builds the message subject and body
|
||||||
func (provider *AlertProvider) buildMessageSubjectAndBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) (string, string) {
|
func (provider *AlertProvider) buildMessageSubjectAndBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) (string, string) {
|
||||||
var subject, message, results string
|
var subject, message string
|
||||||
if resolved {
|
if resolved {
|
||||||
subject = fmt.Sprintf("[%s] Alert resolved", endpoint.DisplayName())
|
subject = fmt.Sprintf("[%s] Alert resolved", endpoint.DisplayName())
|
||||||
message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||||
@ -120,20 +119,24 @@ func (provider *AlertProvider) buildMessageSubjectAndBody(endpoint *core.Endpoin
|
|||||||
subject = fmt.Sprintf("[%s] Alert triggered", endpoint.DisplayName())
|
subject = fmt.Sprintf("[%s] Alert triggered", endpoint.DisplayName())
|
||||||
message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
||||||
}
|
}
|
||||||
for _, conditionResult := range result.ConditionResults {
|
var formattedConditionResults string
|
||||||
var prefix string
|
if len(result.ConditionResults) > 0 {
|
||||||
if conditionResult.Success {
|
formattedConditionResults = "\n\nCondition results:\n"
|
||||||
prefix = "✅"
|
for _, conditionResult := range result.ConditionResults {
|
||||||
} else {
|
var prefix string
|
||||||
prefix = "❌"
|
if conditionResult.Success {
|
||||||
|
prefix = "✅"
|
||||||
|
} else {
|
||||||
|
prefix = "❌"
|
||||||
|
}
|
||||||
|
formattedConditionResults += fmt.Sprintf("%s %s\n", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("%s %s\n", prefix, conditionResult.Condition)
|
|
||||||
}
|
}
|
||||||
var description string
|
var description string
|
||||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||||
description = "\n\nAlert description: " + alertDescription
|
description = "\n\nAlert description: " + alertDescription
|
||||||
}
|
}
|
||||||
return subject, message + description + "\n\nCondition results:\n" + results
|
return subject, message + description + formattedConditionResults
|
||||||
}
|
}
|
||||||
|
|
||||||
// getToForGroup returns the appropriate email integration to for a given group
|
// getToForGroup returns the appropriate email integration to for a given group
|
||||||
|
@ -75,7 +75,7 @@ type Embed struct {
|
|||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Color int `json:"color"`
|
Color int `json:"color"`
|
||||||
Fields []Field `json:"fields"`
|
Fields []Field `json:"fields,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
@ -86,7 +86,7 @@ type Field struct {
|
|||||||
|
|
||||||
// buildRequestBody builds the request body for the provider
|
// buildRequestBody builds the request body for the provider
|
||||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||||
var message, results string
|
var message string
|
||||||
var colorCode int
|
var colorCode int
|
||||||
if resolved {
|
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)
|
message = fmt.Sprintf("An alert for **%s** has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||||
@ -95,6 +95,7 @@ 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)
|
message = fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
||||||
colorCode = 15158332
|
colorCode = 15158332
|
||||||
}
|
}
|
||||||
|
var formattedConditionResults string
|
||||||
for _, conditionResult := range result.ConditionResults {
|
for _, conditionResult := range result.ConditionResults {
|
||||||
var prefix string
|
var prefix string
|
||||||
if conditionResult.Success {
|
if conditionResult.Success {
|
||||||
@ -102,7 +103,7 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
} else {
|
} else {
|
||||||
prefix = ":x:"
|
prefix = ":x:"
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
formattedConditionResults += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
var description string
|
var description string
|
||||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||||
@ -112,24 +113,25 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
if provider.Title != "" {
|
if provider.Title != "" {
|
||||||
title = provider.Title
|
title = provider.Title
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(Body{
|
body := Body{
|
||||||
Content: "",
|
Content: "",
|
||||||
Embeds: []Embed{
|
Embeds: []Embed{
|
||||||
{
|
{
|
||||||
Title: title,
|
Title: title,
|
||||||
Description: message + description,
|
Description: message + description,
|
||||||
Color: colorCode,
|
Color: colorCode,
|
||||||
Fields: []Field{
|
|
||||||
{
|
|
||||||
Name: "Condition results",
|
|
||||||
Value: results,
|
|
||||||
Inline: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
return body
|
if len(formattedConditionResults) > 0 {
|
||||||
|
body.Embeds[0].Fields = append(body.Embeds[0].Fields, Field{
|
||||||
|
Name: "Condition results",
|
||||||
|
Value: formattedConditionResults,
|
||||||
|
Inline: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
bodyAsJSON, _ := json.Marshal(body)
|
||||||
|
return bodyAsJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
||||||
|
@ -155,6 +155,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||||||
Name string
|
Name string
|
||||||
Provider AlertProvider
|
Provider AlertProvider
|
||||||
Alert alert.Alert
|
Alert alert.Alert
|
||||||
|
NoConditions bool
|
||||||
Resolved bool
|
Resolved bool
|
||||||
ExpectedBody string
|
ExpectedBody string
|
||||||
}{
|
}{
|
||||||
@ -179,18 +180,30 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||||||
Resolved: false,
|
Resolved: false,
|
||||||
ExpectedBody: "{\"content\":\"\",\"embeds\":[{\"title\":\"provider-title\",\"description\":\"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"color\":15158332,\"fields\":[{\"name\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n:x: - `[BODY] != \\\"\\\"`\\n\",\"inline\":false}]}]}",
|
ExpectedBody: "{\"content\":\"\",\"embeds\":[{\"title\":\"provider-title\",\"description\":\"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"color\":15158332,\"fields\":[{\"name\":\"Condition results\",\"value\":\":x: - `[CONNECTED] == true`\\n:x: - `[STATUS] == 200`\\n:x: - `[BODY] != \\\"\\\"`\\n\",\"inline\":false}]}]}",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "triggered-with-no-conditions",
|
||||||
|
NoConditions: true,
|
||||||
|
Provider: AlertProvider{Title: title},
|
||||||
|
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||||
|
Resolved: false,
|
||||||
|
ExpectedBody: "{\"content\":\"\",\"embeds\":[{\"title\":\"provider-title\",\"description\":\"An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"color\":15158332}]}",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, scenario := range scenarios {
|
for _, scenario := range scenarios {
|
||||||
t.Run(scenario.Name, func(t *testing.T) {
|
t.Run(scenario.Name, func(t *testing.T) {
|
||||||
|
var conditionResults []*core.ConditionResult
|
||||||
|
if !scenario.NoConditions {
|
||||||
|
conditionResults = []*core.ConditionResult{
|
||||||
|
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
||||||
|
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
||||||
|
{Condition: "[BODY] != \"\"", Success: scenario.Resolved},
|
||||||
|
}
|
||||||
|
}
|
||||||
body := scenario.Provider.buildRequestBody(
|
body := scenario.Provider.buildRequestBody(
|
||||||
&core.Endpoint{Name: "endpoint-name"},
|
&core.Endpoint{Name: "endpoint-name"},
|
||||||
&scenario.Alert,
|
&scenario.Alert,
|
||||||
&core.Result{
|
&core.Result{
|
||||||
ConditionResults: []*core.ConditionResult{
|
ConditionResults: conditionResults,
|
||||||
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
|
||||||
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
|
||||||
{Condition: "[BODY] != \"\"", Success: scenario.Resolved},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
scenario.Resolved,
|
scenario.Resolved,
|
||||||
)
|
)
|
||||||
|
@ -88,7 +88,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
|||||||
|
|
||||||
// buildMessageSubjectAndBody builds the message subject and body
|
// buildMessageSubjectAndBody builds the message subject and body
|
||||||
func (provider *AlertProvider) buildMessageSubjectAndBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) (string, string) {
|
func (provider *AlertProvider) buildMessageSubjectAndBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) (string, string) {
|
||||||
var subject, message, results string
|
var subject, message string
|
||||||
if resolved {
|
if resolved {
|
||||||
subject = fmt.Sprintf("[%s] Alert resolved", endpoint.DisplayName())
|
subject = fmt.Sprintf("[%s] Alert resolved", endpoint.DisplayName())
|
||||||
message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
message = fmt.Sprintf("An alert for %s has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||||
@ -96,20 +96,24 @@ func (provider *AlertProvider) buildMessageSubjectAndBody(endpoint *core.Endpoin
|
|||||||
subject = fmt.Sprintf("[%s] Alert triggered", endpoint.DisplayName())
|
subject = fmt.Sprintf("[%s] Alert triggered", endpoint.DisplayName())
|
||||||
message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
message = fmt.Sprintf("An alert for %s has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
||||||
}
|
}
|
||||||
for _, conditionResult := range result.ConditionResults {
|
var formattedConditionResults string
|
||||||
var prefix string
|
if len(result.ConditionResults) > 0 {
|
||||||
if conditionResult.Success {
|
formattedConditionResults = "\n\nCondition results:\n"
|
||||||
prefix = "✅"
|
for _, conditionResult := range result.ConditionResults {
|
||||||
} else {
|
var prefix string
|
||||||
prefix = "❌"
|
if conditionResult.Success {
|
||||||
|
prefix = "✅"
|
||||||
|
} else {
|
||||||
|
prefix = "❌"
|
||||||
|
}
|
||||||
|
formattedConditionResults += fmt.Sprintf("%s %s\n", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("%s %s\n", prefix, conditionResult.Condition)
|
|
||||||
}
|
}
|
||||||
var description string
|
var description string
|
||||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||||
description = "\n\nAlert description: " + alertDescription
|
description = "\n\nAlert description: " + alertDescription
|
||||||
}
|
}
|
||||||
return subject, message + description + "\n\nCondition results:\n" + results
|
return subject, message + description + formattedConditionResults
|
||||||
}
|
}
|
||||||
|
|
||||||
// getToForGroup returns the appropriate email integration to for a given group
|
// getToForGroup returns the appropriate email integration to for a given group
|
||||||
|
@ -105,22 +105,25 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
|||||||
|
|
||||||
// buildIssueBody builds the body of the issue
|
// buildIssueBody builds the body of the issue
|
||||||
func (provider *AlertProvider) buildIssueBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result) string {
|
func (provider *AlertProvider) buildIssueBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result) string {
|
||||||
var results string
|
var formattedConditionResults string
|
||||||
for _, conditionResult := range result.ConditionResults {
|
if len(result.ConditionResults) > 0 {
|
||||||
var prefix string
|
formattedConditionResults = "\n\n## Condition results\n"
|
||||||
if conditionResult.Success {
|
for _, conditionResult := range result.ConditionResults {
|
||||||
prefix = ":white_check_mark:"
|
var prefix string
|
||||||
} else {
|
if conditionResult.Success {
|
||||||
prefix = ":x:"
|
prefix = ":white_check_mark:"
|
||||||
|
} else {
|
||||||
|
prefix = ":x:"
|
||||||
|
}
|
||||||
|
formattedConditionResults += fmt.Sprintf("- %s - `%s`\n", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("- %s - `%s`\n", prefix, conditionResult.Condition)
|
|
||||||
}
|
}
|
||||||
var description string
|
var description string
|
||||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||||
description = ":\n> " + alertDescription
|
description = ":\n> " + alertDescription
|
||||||
}
|
}
|
||||||
message := fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
message := fmt.Sprintf("An alert for **%s** has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
||||||
return message + description + "\n\n## Condition results\n" + results
|
return message + description + formattedConditionResults
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultAlert returns the provider's default alert configuration
|
// GetDefaultAlert returns the provider's default alert configuration
|
||||||
|
@ -112,6 +112,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||||||
Endpoint core.Endpoint
|
Endpoint core.Endpoint
|
||||||
Provider AlertProvider
|
Provider AlertProvider
|
||||||
Alert alert.Alert
|
Alert alert.Alert
|
||||||
|
NoConditions bool
|
||||||
ExpectedBody string
|
ExpectedBody string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -122,24 +123,34 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||||||
ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\n> description-1\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`",
|
ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 3 time(s) in a row:\n> description-1\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "no-description",
|
Name: "triggered-with-no-description",
|
||||||
Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"},
|
Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"},
|
||||||
Provider: AlertProvider{},
|
Provider: AlertProvider{},
|
||||||
Alert: alert.Alert{FailureThreshold: 10},
|
Alert: alert.Alert{FailureThreshold: 10},
|
||||||
ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 10 time(s) in a row\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`",
|
ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 10 time(s) in a row\n\n## Condition results\n- :white_check_mark: - `[CONNECTED] == true`\n- :x: - `[STATUS] == 200`",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "triggered-with-no-conditions",
|
||||||
|
NoConditions: true,
|
||||||
|
Endpoint: core.Endpoint{Name: "endpoint-name", URL: "https://example.org"},
|
||||||
|
Provider: AlertProvider{},
|
||||||
|
Alert: alert.Alert{Description: &firstDescription, FailureThreshold: 10},
|
||||||
|
ExpectedBody: "An alert for **endpoint-name** has been triggered due to having failed 10 time(s) in a row:\n> description-1",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, scenario := range scenarios {
|
for _, scenario := range scenarios {
|
||||||
t.Run(scenario.Name, func(t *testing.T) {
|
t.Run(scenario.Name, func(t *testing.T) {
|
||||||
|
var conditionResults []*core.ConditionResult
|
||||||
|
if !scenario.NoConditions {
|
||||||
|
conditionResults = []*core.ConditionResult{
|
||||||
|
{Condition: "[CONNECTED] == true", Success: true},
|
||||||
|
{Condition: "[STATUS] == 200", Success: false},
|
||||||
|
}
|
||||||
|
}
|
||||||
body := scenario.Provider.buildIssueBody(
|
body := scenario.Provider.buildIssueBody(
|
||||||
&scenario.Endpoint,
|
&scenario.Endpoint,
|
||||||
&scenario.Alert,
|
&scenario.Alert,
|
||||||
&core.Result{
|
&core.Result{ConditionResults: conditionResults},
|
||||||
ConditionResults: []*core.ConditionResult{
|
|
||||||
{Condition: "[CONNECTED] == true", Success: true},
|
|
||||||
{Condition: "[STATUS] == 200", Success: false},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if strings.TrimSpace(body) != strings.TrimSpace(scenario.ExpectedBody) {
|
if strings.TrimSpace(body) != strings.TrimSpace(scenario.ExpectedBody) {
|
||||||
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
t.Errorf("expected:\n%s\ngot:\n%s", scenario.ExpectedBody, body)
|
||||||
|
@ -25,10 +25,13 @@ type AlertProvider struct {
|
|||||||
|
|
||||||
// Severity can be one of: critical, high, medium, low, info, unknown. Defaults to critical
|
// Severity can be one of: critical, high, medium, low, info, unknown. Defaults to critical
|
||||||
Severity string `yaml:"severity,omitempty"`
|
Severity string `yaml:"severity,omitempty"`
|
||||||
|
|
||||||
// MonitoringTool overrides the name sent to gitlab. Defaults to gatus
|
// MonitoringTool overrides the name sent to gitlab. Defaults to gatus
|
||||||
MonitoringTool string `yaml:"monitoring-tool,omitempty"`
|
MonitoringTool string `yaml:"monitoring-tool,omitempty"`
|
||||||
|
|
||||||
// EnvironmentName is the name of the associated GitLab environment. Required to display alerts on a dashboard.
|
// EnvironmentName is the name of the associated GitLab environment. Required to display alerts on a dashboard.
|
||||||
EnvironmentName string `yaml:"environment-name,omitempty"`
|
EnvironmentName string `yaml:"environment-name,omitempty"`
|
||||||
|
|
||||||
// Service affected. Defaults to endpoint display name
|
// Service affected. Defaults to endpoint display name
|
||||||
Service string `yaml:"service,omitempty"`
|
Service string `yaml:"service,omitempty"`
|
||||||
}
|
}
|
||||||
@ -52,7 +55,6 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
|||||||
if len(alert.ResolveKey) == 0 {
|
if len(alert.ResolveKey) == 0 {
|
||||||
alert.ResolveKey = uuid.NewString()
|
alert.ResolveKey = uuid.NewString()
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer := bytes.NewBuffer(provider.buildAlertBody(endpoint, alert, result, resolved))
|
buffer := bytes.NewBuffer(provider.buildAlertBody(endpoint, alert, result, resolved))
|
||||||
request, err := http.NewRequest(http.MethodPost, provider.WebhookURL, buffer)
|
request, err := http.NewRequest(http.MethodPost, provider.WebhookURL, buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -114,16 +116,18 @@ func (provider *AlertProvider) buildAlertBody(endpoint *core.Endpoint, alert *al
|
|||||||
if resolved {
|
if resolved {
|
||||||
body.EndTime = result.Timestamp.Format(time.RFC3339)
|
body.EndTime = result.Timestamp.Format(time.RFC3339)
|
||||||
}
|
}
|
||||||
|
var formattedConditionResults string
|
||||||
var results string
|
if len(result.ConditionResults) > 0 {
|
||||||
for _, conditionResult := range result.ConditionResults {
|
formattedConditionResults = "\n\n## Condition results\n"
|
||||||
var prefix string
|
for _, conditionResult := range result.ConditionResults {
|
||||||
if conditionResult.Success {
|
var prefix string
|
||||||
prefix = ":white_check_mark:"
|
if conditionResult.Success {
|
||||||
} else {
|
prefix = ":white_check_mark:"
|
||||||
prefix = ":x:"
|
} else {
|
||||||
|
prefix = ":x:"
|
||||||
|
}
|
||||||
|
formattedConditionResults += fmt.Sprintf("- %s - `%s`\n", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("- %s - `%s`\n", prefix, conditionResult.Condition)
|
|
||||||
}
|
}
|
||||||
var description string
|
var description string
|
||||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||||
@ -135,10 +139,9 @@ func (provider *AlertProvider) buildAlertBody(endpoint *core.Endpoint, alert *al
|
|||||||
} else {
|
} else {
|
||||||
message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
||||||
}
|
}
|
||||||
body.Description = message + description + "\n\n## Condition results\n" + results
|
body.Description = message + description + formattedConditionResults
|
||||||
|
bodyAsJSON, _ := json.Marshal(body)
|
||||||
json, _ := json.Marshal(body)
|
return bodyAsJSON
|
||||||
return json
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultAlert returns the provider's default alert configuration
|
// GetDefaultAlert returns the provider's default alert configuration
|
||||||
|
@ -121,7 +121,7 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
color = "#DD0000"
|
color = "#DD0000"
|
||||||
message = fmt.Sprintf("<font color='%s'>An alert has been triggered due to having failed %d time(s) in a row</font>", color, alert.FailureThreshold)
|
message = fmt.Sprintf("<font color='%s'>An alert has been triggered due to having failed %d time(s) in a row</font>", color, alert.FailureThreshold)
|
||||||
}
|
}
|
||||||
var results string
|
var formattedConditionResults string
|
||||||
for _, conditionResult := range result.ConditionResults {
|
for _, conditionResult := range result.ConditionResults {
|
||||||
var prefix string
|
var prefix string
|
||||||
if conditionResult.Success {
|
if conditionResult.Success {
|
||||||
@ -129,7 +129,7 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
} else {
|
} else {
|
||||||
prefix = "❌"
|
prefix = "❌"
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("%s %s<br>", prefix, conditionResult.Condition)
|
formattedConditionResults += fmt.Sprintf("%s %s<br>", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
var description string
|
var description string
|
||||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||||
@ -150,20 +150,22 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
Icon: "BOOKMARK",
|
Icon: "BOOKMARK",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
KeyValue: &KeyValue{
|
|
||||||
TopLabel: "Condition results",
|
|
||||||
Content: results,
|
|
||||||
ContentMultiline: "true",
|
|
||||||
Icon: "DESCRIPTION",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if len(formattedConditionResults) > 0 {
|
||||||
|
payload.Cards[0].Sections[0].Widgets = append(payload.Cards[0].Sections[0].Widgets, Widgets{
|
||||||
|
KeyValue: &KeyValue{
|
||||||
|
TopLabel: "Condition results",
|
||||||
|
Content: formattedConditionResults,
|
||||||
|
ContentMultiline: "true",
|
||||||
|
Icon: "DESCRIPTION",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
if endpoint.Type() == core.EndpointTypeHTTP {
|
if endpoint.Type() == core.EndpointTypeHTTP {
|
||||||
// We only include a button targeting the URL if the endpoint is an HTTP endpoint
|
// We only include a button targeting the URL if the endpoint is an HTTP endpoint
|
||||||
// If the URL isn't prefixed with https://, Google Chat will just display a blank message aynways.
|
// If the URL isn't prefixed with https://, Google Chat will just display a blank message aynways.
|
||||||
@ -179,8 +181,8 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(payload)
|
bodyAsJSON, _ := json.Marshal(payload)
|
||||||
return body
|
return bodyAsJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
||||||
|
@ -68,12 +68,13 @@ type Body struct {
|
|||||||
|
|
||||||
// buildRequestBody builds the request body for the provider
|
// buildRequestBody builds the request body for the provider
|
||||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||||
var message, results string
|
var message string
|
||||||
if resolved {
|
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)
|
message = fmt.Sprintf("An alert for `%s` has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||||
} else {
|
} else {
|
||||||
message = fmt.Sprintf("An alert for `%s` has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
message = fmt.Sprintf("An alert for `%s` has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
||||||
}
|
}
|
||||||
|
var formattedConditionResults string
|
||||||
for _, conditionResult := range result.ConditionResults {
|
for _, conditionResult := range result.ConditionResults {
|
||||||
var prefix string
|
var prefix string
|
||||||
if conditionResult.Success {
|
if conditionResult.Success {
|
||||||
@ -81,22 +82,22 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
} else {
|
} else {
|
||||||
prefix = "✕"
|
prefix = "✕"
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("\n%s - %s", prefix, conditionResult.Condition)
|
formattedConditionResults += fmt.Sprintf("\n%s - %s", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
if len(alert.GetDescription()) > 0 {
|
if len(alert.GetDescription()) > 0 {
|
||||||
message += " with the following description: " + alert.GetDescription()
|
message += " with the following description: " + alert.GetDescription()
|
||||||
}
|
}
|
||||||
message += results
|
message += formattedConditionResults
|
||||||
title := "Gatus: " + endpoint.DisplayName()
|
title := "Gatus: " + endpoint.DisplayName()
|
||||||
if provider.Title != "" {
|
if provider.Title != "" {
|
||||||
title = provider.Title
|
title = provider.Title
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(Body{
|
bodyAsJSON, _ := json.Marshal(Body{
|
||||||
Message: message,
|
Message: message,
|
||||||
Title: title,
|
Title: title,
|
||||||
Priority: provider.Priority,
|
Priority: provider.Priority,
|
||||||
})
|
})
|
||||||
return body
|
return bodyAsJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultAlert returns the provider's default alert configuration
|
// GetDefaultAlert returns the provider's default alert configuration
|
||||||
|
@ -17,8 +17,10 @@ type AlertProvider struct {
|
|||||||
Project string `yaml:"project"` // JetBrains Space Project name
|
Project string `yaml:"project"` // JetBrains Space Project name
|
||||||
ChannelID string `yaml:"channel-id"` // JetBrains Space Chat Channel ID
|
ChannelID string `yaml:"channel-id"` // JetBrains Space Chat Channel ID
|
||||||
Token string `yaml:"token"` // JetBrains Space Bearer Token
|
Token string `yaml:"token"` // JetBrains Space Bearer Token
|
||||||
// DefaultAlert is the defarlt alert configuration to use for endpoints with an alert of the appropriate type
|
|
||||||
|
// DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type
|
||||||
DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"`
|
DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"`
|
||||||
|
|
||||||
// Overrides is a list of Override that may be prioritized over the default configuration
|
// Overrides is a list of Override that may be prioritized over the default configuration
|
||||||
Overrides []Override `yaml:"overrides,omitempty"`
|
Overrides []Override `yaml:"overrides,omitempty"`
|
||||||
}
|
}
|
||||||
@ -73,7 +75,7 @@ type Body struct {
|
|||||||
type Content struct {
|
type Content struct {
|
||||||
ClassName string `json:"className"`
|
ClassName string `json:"className"`
|
||||||
Style string `json:"style"`
|
Style string `json:"style"`
|
||||||
Sections []Section `json:"sections"`
|
Sections []Section `json:"sections,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Section struct {
|
type Section struct {
|
||||||
@ -112,7 +114,6 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if resolved {
|
if resolved {
|
||||||
body.Content.Style = "SUCCESS"
|
body.Content.Style = "SUCCESS"
|
||||||
body.Content.Sections[0].Header = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
body.Content.Sections[0].Header = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||||
@ -120,7 +121,6 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
body.Content.Style = "WARNING"
|
body.Content.Style = "WARNING"
|
||||||
body.Content.Sections[0].Header = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
body.Content.Sections[0].Header = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, conditionResult := range result.ConditionResults {
|
for _, conditionResult := range result.ConditionResults {
|
||||||
icon := "warning"
|
icon := "warning"
|
||||||
style := "WARNING"
|
style := "WARNING"
|
||||||
@ -128,7 +128,6 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
icon = "success"
|
icon = "success"
|
||||||
style = "SUCCESS"
|
style = "SUCCESS"
|
||||||
}
|
}
|
||||||
|
|
||||||
body.Content.Sections[0].Elements = append(body.Content.Sections[0].Elements, Element{
|
body.Content.Sections[0].Elements = append(body.Content.Sections[0].Elements, Element{
|
||||||
ClassName: "MessageText",
|
ClassName: "MessageText",
|
||||||
Accessory: Accessory{
|
Accessory: Accessory{
|
||||||
@ -141,9 +140,8 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
Content: conditionResult.Condition,
|
Content: conditionResult.Condition,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
bodyAsJSON, _ := json.Marshal(body)
|
||||||
jsonBody, _ := json.Marshal(body)
|
return bodyAsJSON
|
||||||
return jsonBody
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getChannelIDForGroup returns the appropriate channel ID to for a given group override
|
// getChannelIDForGroup returns the appropriate channel ID to for a given group override
|
@ -17,7 +17,7 @@ import (
|
|||||||
|
|
||||||
// AlertProvider is the configuration necessary for sending an alert using Matrix
|
// AlertProvider is the configuration necessary for sending an alert using Matrix
|
||||||
type AlertProvider struct {
|
type AlertProvider struct {
|
||||||
MatrixProviderConfig `yaml:",inline"`
|
ProviderConfig `yaml:",inline"`
|
||||||
|
|
||||||
// DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type
|
// DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type
|
||||||
DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"`
|
DefaultAlert *alert.Alert `yaml:"default-alert,omitempty"`
|
||||||
@ -30,12 +30,12 @@ type AlertProvider struct {
|
|||||||
type Override struct {
|
type Override struct {
|
||||||
Group string `yaml:"group"`
|
Group string `yaml:"group"`
|
||||||
|
|
||||||
MatrixProviderConfig `yaml:",inline"`
|
ProviderConfig `yaml:",inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultHomeserverURL = "https://matrix-client.matrix.org"
|
const defaultServerURL = "https://matrix-client.matrix.org"
|
||||||
|
|
||||||
type MatrixProviderConfig struct {
|
type ProviderConfig struct {
|
||||||
// ServerURL is the custom homeserver to use (optional)
|
// ServerURL is the custom homeserver to use (optional)
|
||||||
ServerURL string `yaml:"server-url"`
|
ServerURL string `yaml:"server-url"`
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ func (provider *AlertProvider) Send(endpoint *core.Endpoint, alert *alert.Alert,
|
|||||||
buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved))
|
buffer := bytes.NewBuffer(provider.buildRequestBody(endpoint, alert, result, resolved))
|
||||||
config := provider.getConfigForGroup(endpoint.Group)
|
config := provider.getConfigForGroup(endpoint.Group)
|
||||||
if config.ServerURL == "" {
|
if config.ServerURL == "" {
|
||||||
config.ServerURL = defaultHomeserverURL
|
config.ServerURL = defaultServerURL
|
||||||
}
|
}
|
||||||
// The Matrix endpoint requires a unique transaction ID for each event sent
|
// The Matrix endpoint requires a unique transaction ID for each event sent
|
||||||
txnId := randStringBytes(24)
|
txnId := randStringBytes(24)
|
||||||
@ -115,12 +115,13 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
|
|
||||||
// buildPlaintextMessageBody builds the message body in plaintext to include in request
|
// buildPlaintextMessageBody builds the message body in plaintext to include in request
|
||||||
func buildPlaintextMessageBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
func buildPlaintextMessageBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
||||||
var message, results string
|
var message string
|
||||||
if resolved {
|
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)
|
message = fmt.Sprintf("An alert for `%s` has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||||
} else {
|
} else {
|
||||||
message = fmt.Sprintf("An alert for `%s` has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
message = fmt.Sprintf("An alert for `%s` has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
||||||
}
|
}
|
||||||
|
var formattedConditionResults string
|
||||||
for _, conditionResult := range result.ConditionResults {
|
for _, conditionResult := range result.ConditionResults {
|
||||||
var prefix string
|
var prefix string
|
||||||
if conditionResult.Success {
|
if conditionResult.Success {
|
||||||
@ -128,49 +129,54 @@ func buildPlaintextMessageBody(endpoint *core.Endpoint, alert *alert.Alert, resu
|
|||||||
} else {
|
} else {
|
||||||
prefix = "✕"
|
prefix = "✕"
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("\n%s - %s", prefix, conditionResult.Condition)
|
formattedConditionResults += fmt.Sprintf("\n%s - %s", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
var description string
|
var description string
|
||||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||||
description = "\n" + alertDescription
|
description = "\n" + alertDescription
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s%s\n%s", message, description, results)
|
return fmt.Sprintf("%s%s\n%s", message, description, formattedConditionResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildHTMLMessageBody builds the message body in HTML to include in request
|
// buildHTMLMessageBody builds the message body in HTML to include in request
|
||||||
func buildHTMLMessageBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
func buildHTMLMessageBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) string {
|
||||||
var message, results string
|
var message string
|
||||||
if resolved {
|
if resolved {
|
||||||
message = fmt.Sprintf("An alert for <code>%s</code> has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
message = fmt.Sprintf("An alert for <code>%s</code> has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||||
} else {
|
} else {
|
||||||
message = fmt.Sprintf("An alert for <code>%s</code> has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
message = fmt.Sprintf("An alert for <code>%s</code> has been triggered due to having failed %d time(s) in a row", endpoint.DisplayName(), alert.FailureThreshold)
|
||||||
}
|
}
|
||||||
for _, conditionResult := range result.ConditionResults {
|
var formattedConditionResults string
|
||||||
var prefix string
|
if len(result.ConditionResults) > 0 {
|
||||||
if conditionResult.Success {
|
formattedConditionResults = "\n<h5>Condition results</h5><ul>"
|
||||||
prefix = "✅"
|
for _, conditionResult := range result.ConditionResults {
|
||||||
} else {
|
var prefix string
|
||||||
prefix = "❌"
|
if conditionResult.Success {
|
||||||
|
prefix = "✅"
|
||||||
|
} else {
|
||||||
|
prefix = "❌"
|
||||||
|
}
|
||||||
|
formattedConditionResults += fmt.Sprintf("<li>%s - <code>%s</code></li>", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("<li>%s - <code>%s</code></li>", prefix, conditionResult.Condition)
|
formattedConditionResults += "</ul>"
|
||||||
}
|
}
|
||||||
var description string
|
var description string
|
||||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||||
description = fmt.Sprintf("\n<blockquote>%s</blockquote>", alertDescription)
|
description = fmt.Sprintf("\n<blockquote>%s</blockquote>", alertDescription)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("<h3>%s</h3>%s\n<h5>Condition results</h5><ul>%s</ul>", message, description, results)
|
return fmt.Sprintf("<h3>%s</h3>%s%s", message, description, formattedConditionResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getConfigForGroup returns the appropriate configuration for a given group
|
// getConfigForGroup returns the appropriate configuration for a given group
|
||||||
func (provider *AlertProvider) getConfigForGroup(group string) MatrixProviderConfig {
|
func (provider *AlertProvider) getConfigForGroup(group string) ProviderConfig {
|
||||||
if provider.Overrides != nil {
|
if provider.Overrides != nil {
|
||||||
for _, override := range provider.Overrides {
|
for _, override := range provider.Overrides {
|
||||||
if group == override.Group {
|
if group == override.Group {
|
||||||
return override.MatrixProviderConfig
|
return override.ProviderConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return provider.MatrixProviderConfig
|
return provider.ProviderConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func randStringBytes(n int) string {
|
func randStringBytes(n int) string {
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
func TestAlertProvider_IsValid(t *testing.T) {
|
func TestAlertProvider_IsValid(t *testing.T) {
|
||||||
invalidProvider := AlertProvider{
|
invalidProvider := AlertProvider{
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
AccessToken: "",
|
AccessToken: "",
|
||||||
InternalRoomID: "",
|
InternalRoomID: "",
|
||||||
},
|
},
|
||||||
@ -22,7 +22,7 @@ func TestAlertProvider_IsValid(t *testing.T) {
|
|||||||
t.Error("provider shouldn't have been valid")
|
t.Error("provider shouldn't have been valid")
|
||||||
}
|
}
|
||||||
validProvider := AlertProvider{
|
validProvider := AlertProvider{
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
},
|
},
|
||||||
@ -31,7 +31,7 @@ func TestAlertProvider_IsValid(t *testing.T) {
|
|||||||
t.Error("provider should've been valid")
|
t.Error("provider should've been valid")
|
||||||
}
|
}
|
||||||
validProviderWithHomeserver := AlertProvider{
|
validProviderWithHomeserver := AlertProvider{
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
ServerURL: "https://example.com",
|
ServerURL: "https://example.com",
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
@ -47,7 +47,7 @@ func TestAlertProvider_IsValidWithOverride(t *testing.T) {
|
|||||||
Overrides: []Override{
|
Overrides: []Override{
|
||||||
{
|
{
|
||||||
Group: "",
|
Group: "",
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
AccessToken: "",
|
AccessToken: "",
|
||||||
InternalRoomID: "",
|
InternalRoomID: "",
|
||||||
},
|
},
|
||||||
@ -61,7 +61,7 @@ func TestAlertProvider_IsValidWithOverride(t *testing.T) {
|
|||||||
Overrides: []Override{
|
Overrides: []Override{
|
||||||
{
|
{
|
||||||
Group: "group",
|
Group: "group",
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
AccessToken: "",
|
AccessToken: "",
|
||||||
InternalRoomID: "",
|
InternalRoomID: "",
|
||||||
},
|
},
|
||||||
@ -72,14 +72,14 @@ func TestAlertProvider_IsValidWithOverride(t *testing.T) {
|
|||||||
t.Error("provider integration key shouldn't have been valid")
|
t.Error("provider integration key shouldn't have been valid")
|
||||||
}
|
}
|
||||||
providerWithValidOverride := AlertProvider{
|
providerWithValidOverride := AlertProvider{
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
},
|
},
|
||||||
Overrides: []Override{
|
Overrides: []Override{
|
||||||
{
|
{
|
||||||
Group: "group",
|
Group: "group",
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
ServerURL: "https://example.com",
|
ServerURL: "https://example.com",
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
@ -232,12 +232,12 @@ func TestAlertProvider_getConfigForGroup(t *testing.T) {
|
|||||||
Name string
|
Name string
|
||||||
Provider AlertProvider
|
Provider AlertProvider
|
||||||
InputGroup string
|
InputGroup string
|
||||||
ExpectedOutput MatrixProviderConfig
|
ExpectedOutput ProviderConfig
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Name: "provider-no-override-specify-no-group-should-default",
|
Name: "provider-no-override-specify-no-group-should-default",
|
||||||
Provider: AlertProvider{
|
Provider: AlertProvider{
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
ServerURL: "https://example.com",
|
ServerURL: "https://example.com",
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
@ -245,7 +245,7 @@ func TestAlertProvider_getConfigForGroup(t *testing.T) {
|
|||||||
Overrides: nil,
|
Overrides: nil,
|
||||||
},
|
},
|
||||||
InputGroup: "",
|
InputGroup: "",
|
||||||
ExpectedOutput: MatrixProviderConfig{
|
ExpectedOutput: ProviderConfig{
|
||||||
ServerURL: "https://example.com",
|
ServerURL: "https://example.com",
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
@ -254,7 +254,7 @@ func TestAlertProvider_getConfigForGroup(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Name: "provider-no-override-specify-group-should-default",
|
Name: "provider-no-override-specify-group-should-default",
|
||||||
Provider: AlertProvider{
|
Provider: AlertProvider{
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
ServerURL: "https://example.com",
|
ServerURL: "https://example.com",
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
@ -262,7 +262,7 @@ func TestAlertProvider_getConfigForGroup(t *testing.T) {
|
|||||||
Overrides: nil,
|
Overrides: nil,
|
||||||
},
|
},
|
||||||
InputGroup: "group",
|
InputGroup: "group",
|
||||||
ExpectedOutput: MatrixProviderConfig{
|
ExpectedOutput: ProviderConfig{
|
||||||
ServerURL: "https://example.com",
|
ServerURL: "https://example.com",
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
@ -271,7 +271,7 @@ func TestAlertProvider_getConfigForGroup(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Name: "provider-with-override-specify-no-group-should-default",
|
Name: "provider-with-override-specify-no-group-should-default",
|
||||||
Provider: AlertProvider{
|
Provider: AlertProvider{
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
ServerURL: "https://example.com",
|
ServerURL: "https://example.com",
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
@ -279,7 +279,7 @@ func TestAlertProvider_getConfigForGroup(t *testing.T) {
|
|||||||
Overrides: []Override{
|
Overrides: []Override{
|
||||||
{
|
{
|
||||||
Group: "group",
|
Group: "group",
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
ServerURL: "https://example01.com",
|
ServerURL: "https://example01.com",
|
||||||
AccessToken: "12",
|
AccessToken: "12",
|
||||||
InternalRoomID: "!a:example01.com",
|
InternalRoomID: "!a:example01.com",
|
||||||
@ -288,7 +288,7 @@ func TestAlertProvider_getConfigForGroup(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
InputGroup: "",
|
InputGroup: "",
|
||||||
ExpectedOutput: MatrixProviderConfig{
|
ExpectedOutput: ProviderConfig{
|
||||||
ServerURL: "https://example.com",
|
ServerURL: "https://example.com",
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
@ -297,7 +297,7 @@ func TestAlertProvider_getConfigForGroup(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Name: "provider-with-override-specify-group-should-override",
|
Name: "provider-with-override-specify-group-should-override",
|
||||||
Provider: AlertProvider{
|
Provider: AlertProvider{
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
ServerURL: "https://example.com",
|
ServerURL: "https://example.com",
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
@ -305,7 +305,7 @@ func TestAlertProvider_getConfigForGroup(t *testing.T) {
|
|||||||
Overrides: []Override{
|
Overrides: []Override{
|
||||||
{
|
{
|
||||||
Group: "group",
|
Group: "group",
|
||||||
MatrixProviderConfig: MatrixProviderConfig{
|
ProviderConfig: ProviderConfig{
|
||||||
ServerURL: "https://example01.com",
|
ServerURL: "https://example01.com",
|
||||||
AccessToken: "12",
|
AccessToken: "12",
|
||||||
InternalRoomID: "!a:example01.com",
|
InternalRoomID: "!a:example01.com",
|
||||||
@ -314,7 +314,7 @@ func TestAlertProvider_getConfigForGroup(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
InputGroup: "group",
|
InputGroup: "group",
|
||||||
ExpectedOutput: MatrixProviderConfig{
|
ExpectedOutput: ProviderConfig{
|
||||||
ServerURL: "https://example01.com",
|
ServerURL: "https://example01.com",
|
||||||
AccessToken: "12",
|
AccessToken: "12",
|
||||||
InternalRoomID: "!a:example01.com",
|
InternalRoomID: "!a:example01.com",
|
||||||
|
@ -93,7 +93,7 @@ type Field struct {
|
|||||||
|
|
||||||
// buildRequestBody builds the request body for the provider
|
// buildRequestBody builds the request body for the provider
|
||||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||||
var message, color, results string
|
var message, color string
|
||||||
if resolved {
|
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)
|
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"
|
color = "#36A64F"
|
||||||
@ -101,20 +101,23 @@ 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)
|
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"
|
color = "#DD0000"
|
||||||
}
|
}
|
||||||
for _, conditionResult := range result.ConditionResults {
|
var formattedConditionResults string
|
||||||
var prefix string
|
if len(result.ConditionResults) > 0 {
|
||||||
if conditionResult.Success {
|
for _, conditionResult := range result.ConditionResults {
|
||||||
prefix = ":white_check_mark:"
|
var prefix string
|
||||||
} else {
|
if conditionResult.Success {
|
||||||
prefix = ":x:"
|
prefix = ":white_check_mark:"
|
||||||
|
} else {
|
||||||
|
prefix = ":x:"
|
||||||
|
}
|
||||||
|
formattedConditionResults += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
|
||||||
}
|
}
|
||||||
var description string
|
var description string
|
||||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||||
description = ":\n> " + alertDescription
|
description = ":\n> " + alertDescription
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(Body{
|
body := Body{
|
||||||
Text: "",
|
Text: "",
|
||||||
Username: "gatus",
|
Username: "gatus",
|
||||||
IconURL: "https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png",
|
IconURL: "https://raw.githubusercontent.com/TwiN/gatus/master/.github/assets/logo.png",
|
||||||
@ -125,17 +128,18 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
Text: message + description,
|
Text: message + description,
|
||||||
Short: false,
|
Short: false,
|
||||||
Color: color,
|
Color: color,
|
||||||
Fields: []Field{
|
|
||||||
{
|
|
||||||
Title: "Condition results",
|
|
||||||
Value: results,
|
|
||||||
Short: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
return body
|
if len(formattedConditionResults) > 0 {
|
||||||
|
body.Attachments[0].Fields = append(body.Attachments[0].Fields, Field{
|
||||||
|
Title: "Condition results",
|
||||||
|
Value: formattedConditionResults,
|
||||||
|
Short: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
bodyAsJSON, _ := json.Marshal(body)
|
||||||
|
return bodyAsJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
||||||
|
@ -78,7 +78,7 @@ type Body struct {
|
|||||||
|
|
||||||
// buildRequestBody builds the request body for the provider
|
// buildRequestBody builds the request body for the provider
|
||||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||||
var message, results, tag string
|
var message, formattedConditionResults, tag string
|
||||||
if resolved {
|
if resolved {
|
||||||
tag = "white_check_mark"
|
tag = "white_check_mark"
|
||||||
message = "An alert has been resolved after passing successfully " + strconv.Itoa(alert.SuccessThreshold) + " time(s) in a row"
|
message = "An alert has been resolved after passing successfully " + strconv.Itoa(alert.SuccessThreshold) + " time(s) in a row"
|
||||||
@ -93,12 +93,12 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
} else {
|
} else {
|
||||||
prefix = "🔴"
|
prefix = "🔴"
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("\n%s %s", prefix, conditionResult.Condition)
|
formattedConditionResults += fmt.Sprintf("\n%s %s", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
if len(alert.GetDescription()) > 0 {
|
if len(alert.GetDescription()) > 0 {
|
||||||
message += " with the following description: " + alert.GetDescription()
|
message += " with the following description: " + alert.GetDescription()
|
||||||
}
|
}
|
||||||
message += results
|
message += formattedConditionResults
|
||||||
body, _ := json.Marshal(Body{
|
body, _ := json.Marshal(Body{
|
||||||
Topic: provider.Topic,
|
Topic: provider.Topic,
|
||||||
Title: "Gatus: " + endpoint.DisplayName(),
|
Title: "Gatus: " + endpoint.DisplayName(),
|
||||||
|
@ -116,7 +116,7 @@ func (provider *AlertProvider) sendRequest(url, method string, payload interface
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (provider *AlertProvider) buildCreateRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) alertCreateRequest {
|
func (provider *AlertProvider) buildCreateRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) alertCreateRequest {
|
||||||
var message, description, results string
|
var message, description string
|
||||||
if resolved {
|
if resolved {
|
||||||
message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.Name, alert.GetDescription())
|
message = fmt.Sprintf("RESOLVED: %s - %s", endpoint.Name, alert.GetDescription())
|
||||||
description = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
description = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||||
@ -127,6 +127,7 @@ func (provider *AlertProvider) buildCreateRequestBody(endpoint *core.Endpoint, a
|
|||||||
if endpoint.Group != "" {
|
if endpoint.Group != "" {
|
||||||
message = fmt.Sprintf("[%s] %s", endpoint.Group, message)
|
message = fmt.Sprintf("[%s] %s", endpoint.Group, message)
|
||||||
}
|
}
|
||||||
|
var formattedConditionResults string
|
||||||
for _, conditionResult := range result.ConditionResults {
|
for _, conditionResult := range result.ConditionResults {
|
||||||
var prefix string
|
var prefix string
|
||||||
if conditionResult.Success {
|
if conditionResult.Success {
|
||||||
@ -134,9 +135,9 @@ func (provider *AlertProvider) buildCreateRequestBody(endpoint *core.Endpoint, a
|
|||||||
} else {
|
} else {
|
||||||
prefix = "▢"
|
prefix = "▢"
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
formattedConditionResults += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
description = description + "\n" + results
|
description = description + "\n" + formattedConditionResults
|
||||||
key := buildKey(endpoint)
|
key := buildKey(endpoint)
|
||||||
details := map[string]string{
|
details := map[string]string{
|
||||||
"endpoint:url": endpoint.URL,
|
"endpoint:url": endpoint.URL,
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
"github.com/TwiN/gatus/v5/core"
|
"github.com/TwiN/gatus/v5/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AlertProvider is the interface that each providers should implement
|
// AlertProvider is the interface that each provider should implement
|
||||||
type AlertProvider interface {
|
type AlertProvider interface {
|
||||||
// IsValid returns whether the provider's configuration is valid
|
// IsValid returns whether the provider's configuration is valid
|
||||||
IsValid() bool
|
IsValid() bool
|
||||||
|
@ -71,7 +71,7 @@ type Attachment struct {
|
|||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
Short bool `json:"short"`
|
Short bool `json:"short"`
|
||||||
Color string `json:"color"`
|
Color string `json:"color"`
|
||||||
Fields []Field `json:"fields"`
|
Fields []Field `json:"fields,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
@ -82,7 +82,7 @@ type Field struct {
|
|||||||
|
|
||||||
// buildRequestBody builds the request body for the provider
|
// buildRequestBody builds the request body for the provider
|
||||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||||
var message, color, results string
|
var message, color string
|
||||||
if resolved {
|
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)
|
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"
|
color = "#36A64F"
|
||||||
@ -90,6 +90,7 @@ 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)
|
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"
|
color = "#DD0000"
|
||||||
}
|
}
|
||||||
|
var formattedConditionResults string
|
||||||
for _, conditionResult := range result.ConditionResults {
|
for _, conditionResult := range result.ConditionResults {
|
||||||
var prefix string
|
var prefix string
|
||||||
if conditionResult.Success {
|
if conditionResult.Success {
|
||||||
@ -97,13 +98,13 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
} else {
|
} else {
|
||||||
prefix = ":x:"
|
prefix = ":x:"
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
formattedConditionResults += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
var description string
|
var description string
|
||||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||||
description = ":\n> " + alertDescription
|
description = ":\n> " + alertDescription
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(Body{
|
body := Body{
|
||||||
Text: "",
|
Text: "",
|
||||||
Attachments: []Attachment{
|
Attachments: []Attachment{
|
||||||
{
|
{
|
||||||
@ -111,17 +112,18 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
Text: message + description,
|
Text: message + description,
|
||||||
Short: false,
|
Short: false,
|
||||||
Color: color,
|
Color: color,
|
||||||
Fields: []Field{
|
|
||||||
{
|
|
||||||
Title: "Condition results",
|
|
||||||
Value: results,
|
|
||||||
Short: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
return body
|
if len(formattedConditionResults) > 0 {
|
||||||
|
body.Attachments[0].Fields = append(body.Attachments[0].Fields, Field{
|
||||||
|
Title: "Condition results",
|
||||||
|
Value: formattedConditionResults,
|
||||||
|
Short: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
bodyAsJSON, _ := json.Marshal(body)
|
||||||
|
return bodyAsJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
||||||
|
@ -144,6 +144,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||||||
Provider AlertProvider
|
Provider AlertProvider
|
||||||
Endpoint core.Endpoint
|
Endpoint core.Endpoint
|
||||||
Alert alert.Alert
|
Alert alert.Alert
|
||||||
|
NoConditions bool
|
||||||
Resolved bool
|
Resolved bool
|
||||||
ExpectedBody string
|
ExpectedBody string
|
||||||
}{
|
}{
|
||||||
@ -163,6 +164,15 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||||||
Resolved: false,
|
Resolved: false,
|
||||||
ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *group/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}]}]}",
|
ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *group/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: "triggered-with-no-conditions",
|
||||||
|
NoConditions: true,
|
||||||
|
Provider: AlertProvider{},
|
||||||
|
Endpoint: core.Endpoint{Name: "name"},
|
||||||
|
Alert: alert.Alert{Description: &firstDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||||
|
Resolved: false,
|
||||||
|
ExpectedBody: "{\"text\":\"\",\"attachments\":[{\"title\":\":helmet_with_white_cross: Gatus\",\"text\":\"An alert for *name* has been triggered due to having failed 3 time(s) in a row:\\n\\u003e description-1\",\"short\":false,\"color\":\"#DD0000\"}]}",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "resolved",
|
Name: "resolved",
|
||||||
Provider: AlertProvider{},
|
Provider: AlertProvider{},
|
||||||
@ -182,14 +192,18 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, scenario := range scenarios {
|
for _, scenario := range scenarios {
|
||||||
t.Run(scenario.Name, func(t *testing.T) {
|
t.Run(scenario.Name, func(t *testing.T) {
|
||||||
|
var conditionResults []*core.ConditionResult
|
||||||
|
if !scenario.NoConditions {
|
||||||
|
conditionResults = []*core.ConditionResult{
|
||||||
|
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
||||||
|
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
||||||
|
}
|
||||||
|
}
|
||||||
body := scenario.Provider.buildRequestBody(
|
body := scenario.Provider.buildRequestBody(
|
||||||
&scenario.Endpoint,
|
&scenario.Endpoint,
|
||||||
&scenario.Alert,
|
&scenario.Alert,
|
||||||
&core.Result{
|
&core.Result{
|
||||||
ConditionResults: []*core.ConditionResult{
|
ConditionResults: conditionResults,
|
||||||
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
|
||||||
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
scenario.Resolved,
|
scenario.Resolved,
|
||||||
)
|
)
|
||||||
|
@ -69,7 +69,7 @@ type Body struct {
|
|||||||
ThemeColor string `json:"themeColor"`
|
ThemeColor string `json:"themeColor"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
Sections []Section `json:"sections"`
|
Sections []Section `json:"sections,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Section struct {
|
type Section struct {
|
||||||
@ -87,7 +87,7 @@ 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)
|
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"
|
color = "#DD0000"
|
||||||
}
|
}
|
||||||
var results string
|
var formattedConditionResults string
|
||||||
for _, conditionResult := range result.ConditionResults {
|
for _, conditionResult := range result.ConditionResults {
|
||||||
var prefix string
|
var prefix string
|
||||||
if conditionResult.Success {
|
if conditionResult.Success {
|
||||||
@ -95,26 +95,27 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
|
|||||||
} else {
|
} else {
|
||||||
prefix = "❌"
|
prefix = "❌"
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("%s - `%s`<br/>", prefix, conditionResult.Condition)
|
formattedConditionResults += fmt.Sprintf("%s - `%s`<br/>", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
var description string
|
var description string
|
||||||
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
if alertDescription := alert.GetDescription(); len(alertDescription) > 0 {
|
||||||
description = ": " + alertDescription
|
description = ": " + alertDescription
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(Body{
|
body := Body{
|
||||||
Type: "MessageCard",
|
Type: "MessageCard",
|
||||||
Context: "http://schema.org/extensions",
|
Context: "http://schema.org/extensions",
|
||||||
ThemeColor: color,
|
ThemeColor: color,
|
||||||
Title: "🚨 Gatus",
|
Title: "🚨 Gatus",
|
||||||
Text: message + description,
|
Text: message + description,
|
||||||
Sections: []Section{
|
}
|
||||||
{
|
if len(formattedConditionResults) > 0 {
|
||||||
ActivityTitle: "Condition results",
|
body.Sections = append(body.Sections, Section{
|
||||||
Text: results,
|
ActivityTitle: "Condition results",
|
||||||
},
|
Text: formattedConditionResults,
|
||||||
},
|
})
|
||||||
})
|
}
|
||||||
return body
|
bodyAsJSON, _ := json.Marshal(body)
|
||||||
|
return bodyAsJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
// getWebhookURLForGroup returns the appropriate Webhook URL integration to for a given group
|
||||||
|
@ -143,6 +143,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||||||
Name string
|
Name string
|
||||||
Provider AlertProvider
|
Provider AlertProvider
|
||||||
Alert alert.Alert
|
Alert alert.Alert
|
||||||
|
NoConditions bool
|
||||||
Resolved bool
|
Resolved bool
|
||||||
ExpectedBody string
|
ExpectedBody string
|
||||||
}{
|
}{
|
||||||
@ -160,18 +161,28 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||||||
Resolved: true,
|
Resolved: true,
|
||||||
ExpectedBody: "{\"@type\":\"MessageCard\",\"@context\":\"http://schema.org/extensions\",\"themeColor\":\"#36A64F\",\"title\":\"\\u0026#x1F6A8; Gatus\",\"text\":\"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row: description-2\",\"sections\":[{\"activityTitle\":\"Condition results\",\"text\":\"\\u0026#x2705; - `[CONNECTED] == true`\\u003cbr/\\u003e\\u0026#x2705; - `[STATUS] == 200`\\u003cbr/\\u003e\"}]}",
|
ExpectedBody: "{\"@type\":\"MessageCard\",\"@context\":\"http://schema.org/extensions\",\"themeColor\":\"#36A64F\",\"title\":\"\\u0026#x1F6A8; Gatus\",\"text\":\"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row: description-2\",\"sections\":[{\"activityTitle\":\"Condition results\",\"text\":\"\\u0026#x2705; - `[CONNECTED] == true`\\u003cbr/\\u003e\\u0026#x2705; - `[STATUS] == 200`\\u003cbr/\\u003e\"}]}",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "resolved-with-no-conditions",
|
||||||
|
NoConditions: true,
|
||||||
|
Provider: AlertProvider{},
|
||||||
|
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||||
|
Resolved: true,
|
||||||
|
ExpectedBody: "{\"@type\":\"MessageCard\",\"@context\":\"http://schema.org/extensions\",\"themeColor\":\"#36A64F\",\"title\":\"\\u0026#x1F6A8; Gatus\",\"text\":\"An alert for *endpoint-name* has been resolved after passing successfully 5 time(s) in a row: description-2\"}",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, scenario := range scenarios {
|
for _, scenario := range scenarios {
|
||||||
t.Run(scenario.Name, func(t *testing.T) {
|
t.Run(scenario.Name, func(t *testing.T) {
|
||||||
|
var conditionResults []*core.ConditionResult
|
||||||
|
if !scenario.NoConditions {
|
||||||
|
conditionResults = []*core.ConditionResult{
|
||||||
|
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
||||||
|
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
||||||
|
}
|
||||||
|
}
|
||||||
body := scenario.Provider.buildRequestBody(
|
body := scenario.Provider.buildRequestBody(
|
||||||
&core.Endpoint{Name: "endpoint-name"},
|
&core.Endpoint{Name: "endpoint-name"},
|
||||||
&scenario.Alert,
|
&scenario.Alert,
|
||||||
&core.Result{
|
&core.Result{ConditionResults: conditionResults},
|
||||||
ConditionResults: []*core.ConditionResult{
|
|
||||||
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
|
||||||
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scenario.Resolved,
|
scenario.Resolved,
|
||||||
)
|
)
|
||||||
if string(body) != scenario.ExpectedBody {
|
if string(body) != scenario.ExpectedBody {
|
||||||
|
@ -67,33 +67,37 @@ type Body struct {
|
|||||||
|
|
||||||
// buildRequestBody builds the request body for the provider
|
// buildRequestBody builds the request body for the provider
|
||||||
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *alert.Alert, result *core.Result, resolved bool) []byte {
|
||||||
var message, results string
|
var message string
|
||||||
if resolved {
|
if resolved {
|
||||||
message = fmt.Sprintf("An alert for *%s* has been resolved:\n—\n _healthcheck passing successfully %d time(s) in a row_\n— ", endpoint.DisplayName(), alert.SuccessThreshold)
|
message = fmt.Sprintf("An alert for *%s* has been resolved:\n—\n _healthcheck passing successfully %d time(s) in a row_\n— ", endpoint.DisplayName(), alert.SuccessThreshold)
|
||||||
} else {
|
} else {
|
||||||
message = fmt.Sprintf("An alert for *%s* has been triggered:\n—\n _healthcheck failed %d time(s) in a row_\n— ", endpoint.DisplayName(), alert.FailureThreshold)
|
message = fmt.Sprintf("An alert for *%s* has been triggered:\n—\n _healthcheck failed %d time(s) in a row_\n— ", endpoint.DisplayName(), alert.FailureThreshold)
|
||||||
}
|
}
|
||||||
for _, conditionResult := range result.ConditionResults {
|
var formattedConditionResults string
|
||||||
var prefix string
|
if len(result.ConditionResults) > 0 {
|
||||||
if conditionResult.Success {
|
formattedConditionResults = "\n*Condition results*\n"
|
||||||
prefix = "✅"
|
for _, conditionResult := range result.ConditionResults {
|
||||||
} else {
|
var prefix string
|
||||||
prefix = "❌"
|
if conditionResult.Success {
|
||||||
|
prefix = "✅"
|
||||||
|
} else {
|
||||||
|
prefix = "❌"
|
||||||
|
}
|
||||||
|
formattedConditionResults += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
||||||
}
|
}
|
||||||
results += fmt.Sprintf("%s - `%s`\n", prefix, conditionResult.Condition)
|
|
||||||
}
|
}
|
||||||
var text string
|
var text string
|
||||||
if len(alert.GetDescription()) > 0 {
|
if len(alert.GetDescription()) > 0 {
|
||||||
text = fmt.Sprintf("⛑ *Gatus* \n%s \n*Description* \n_%s_ \n\n*Condition results*\n%s", message, alert.GetDescription(), results)
|
text = fmt.Sprintf("⛑ *Gatus* \n%s \n*Description* \n_%s_ \n%s", message, alert.GetDescription(), formattedConditionResults)
|
||||||
} else {
|
} else {
|
||||||
text = fmt.Sprintf("⛑ *Gatus* \n%s \n*Condition results*\n%s", message, results)
|
text = fmt.Sprintf("⛑ *Gatus* \n%s%s", message, formattedConditionResults)
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(Body{
|
bodyAsJSON, _ := json.Marshal(Body{
|
||||||
ChatID: provider.ID,
|
ChatID: provider.ID,
|
||||||
Text: text,
|
Text: text,
|
||||||
ParseMode: "MARKDOWN",
|
ParseMode: "MARKDOWN",
|
||||||
})
|
})
|
||||||
return body
|
return bodyAsJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultAlert returns the provider's default alert configuration
|
// GetDefaultAlert returns the provider's default alert configuration
|
||||||
|
@ -116,6 +116,7 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||||||
Name string
|
Name string
|
||||||
Provider AlertProvider
|
Provider AlertProvider
|
||||||
Alert alert.Alert
|
Alert alert.Alert
|
||||||
|
NoConditions bool
|
||||||
Resolved bool
|
Resolved bool
|
||||||
ExpectedBody string
|
ExpectedBody string
|
||||||
}{
|
}{
|
||||||
@ -133,18 +134,28 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
|
|||||||
Resolved: true,
|
Resolved: true,
|
||||||
ExpectedBody: "{\"chat_id\":\"123\",\"text\":\"⛑ *Gatus* \\nAn alert for *endpoint-name* has been resolved:\\n—\\n _healthcheck passing successfully 5 time(s) in a row_\\n— \\n*Description* \\n_description-2_ \\n\\n*Condition results*\\n✅ - `[CONNECTED] == true`\\n✅ - `[STATUS] == 200`\\n\",\"parse_mode\":\"MARKDOWN\"}",
|
ExpectedBody: "{\"chat_id\":\"123\",\"text\":\"⛑ *Gatus* \\nAn alert for *endpoint-name* has been resolved:\\n—\\n _healthcheck passing successfully 5 time(s) in a row_\\n— \\n*Description* \\n_description-2_ \\n\\n*Condition results*\\n✅ - `[CONNECTED] == true`\\n✅ - `[STATUS] == 200`\\n\",\"parse_mode\":\"MARKDOWN\"}",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "resolved-with-no-conditions",
|
||||||
|
NoConditions: true,
|
||||||
|
Provider: AlertProvider{ID: "123"},
|
||||||
|
Alert: alert.Alert{Description: &secondDescription, SuccessThreshold: 5, FailureThreshold: 3},
|
||||||
|
Resolved: true,
|
||||||
|
ExpectedBody: "{\"chat_id\":\"123\",\"text\":\"⛑ *Gatus* \\nAn alert for *endpoint-name* has been resolved:\\n—\\n _healthcheck passing successfully 5 time(s) in a row_\\n— \\n*Description* \\n_description-2_ \\n\",\"parse_mode\":\"MARKDOWN\"}",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, scenario := range scenarios {
|
for _, scenario := range scenarios {
|
||||||
t.Run(scenario.Name, func(t *testing.T) {
|
t.Run(scenario.Name, func(t *testing.T) {
|
||||||
|
var conditionResults []*core.ConditionResult
|
||||||
|
if !scenario.NoConditions {
|
||||||
|
conditionResults = []*core.ConditionResult{
|
||||||
|
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
||||||
|
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
||||||
|
}
|
||||||
|
}
|
||||||
body := scenario.Provider.buildRequestBody(
|
body := scenario.Provider.buildRequestBody(
|
||||||
&core.Endpoint{Name: "endpoint-name"},
|
&core.Endpoint{Name: "endpoint-name"},
|
||||||
&scenario.Alert,
|
&scenario.Alert,
|
||||||
&core.Result{
|
&core.Result{ConditionResults: conditionResults},
|
||||||
ConditionResults: []*core.ConditionResult{
|
|
||||||
{Condition: "[CONNECTED] == true", Success: scenario.Resolved},
|
|
||||||
{Condition: "[STATUS] == 200", Success: scenario.Resolved},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scenario.Resolved,
|
scenario.Resolved,
|
||||||
)
|
)
|
||||||
if string(body) != scenario.ExpectedBody {
|
if string(body) != scenario.ExpectedBody {
|
||||||
|
@ -377,7 +377,7 @@ func TestHandleAlertingWithProviderThatReturnsAnError(t *testing.T) {
|
|||||||
AlertType: alert.TypeMatrix,
|
AlertType: alert.TypeMatrix,
|
||||||
AlertingConfig: &alerting.Config{
|
AlertingConfig: &alerting.Config{
|
||||||
Matrix: &matrix.AlertProvider{
|
Matrix: &matrix.AlertProvider{
|
||||||
MatrixProviderConfig: matrix.MatrixProviderConfig{
|
ProviderConfig: matrix.ProviderConfig{
|
||||||
ServerURL: "https://example.com",
|
ServerURL: "https://example.com",
|
||||||
AccessToken: "1",
|
AccessToken: "1",
|
||||||
InternalRoomID: "!a:example.com",
|
InternalRoomID: "!a:example.com",
|
||||||
|
Loading…
Reference in New Issue
Block a user