Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/Severity status #487

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga
- [Configuration](#configuration)
- [Conditions](#conditions)
- [Placeholders](#placeholders)
- [Severity](#severity)
- [Functions](#functions)
- [Storage](#storage)
- [Client configuration](#client-configuration)
Expand Down Expand Up @@ -279,7 +280,6 @@ Here are some examples of conditions you can use:
| `[CERTIFICATE_EXPIRATION] > 48h` | Certificate expiration is more than 48h away | 49h, 50h, 123h | 1h, 24h, ... |
| `[DOMAIN_EXPIRATION] > 720h` | The domain must expire in more than 720h | 4000h | 1h, 24h, ... |


#### Placeholders
| Placeholder | Description | Example of resolved value |
|:---------------------------|:------------------------------------------------------------------------------------------|:---------------------------------------------|
Expand All @@ -292,6 +292,16 @@ Here are some examples of conditions you can use:
| `[DOMAIN_EXPIRATION]` | Resolves into the duration before the domain expires (valid units are "s", "m", "h".) | `24h`, `48h`, `1234h56m78s` |
| `[DNS_RCODE]` | Resolves into the DNS status of the response | `NOERROR` |

#### Severity

For failure case you can specify index of severity for each condition. Example of usage: `Low :: [STATUS] == 200`. For case, when severity index is omitted, critical severity will be used.

| Available Severity statuses |
bestwebua marked this conversation as resolved.
Show resolved Hide resolved
|:----------------------------|
|`Low` |
|`Medium` |
|`High` |
|`Critical` |

#### Functions
| Function | Description | Example |
Expand Down
18 changes: 17 additions & 1 deletion alerting/provider/pagerduty/pagerduty.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,28 @@ func (provider *AlertProvider) buildRequestBody(endpoint *core.Endpoint, alert *
Payload: Payload{
Summary: message,
Source: "Gatus",
Severity: "critical",
Severity: provider.pagerDutySeverity(result),
},
})
return body
}

// Returns PagerDuty severity based on result severity represented as a string
func (provider *AlertProvider) pagerDutySeverity(result *core.Result) string {
switch severity := result.Severity; {
case severity.Critical:
return "critical"
case severity.High:
return "error"
case severity.Medium:
return "warning"
case severity.Low:
return "info"
default:
return "critical"
}
}

// getIntegrationKeyForGroup returns the appropriate pagerduty integration key for a given group
func (provider *AlertProvider) getIntegrationKeyForGroup(group string) string {
if provider.Overrides != nil {
Expand Down
20 changes: 20 additions & 0 deletions alerting/provider/pagerduty/pagerduty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,26 @@ func TestAlertProvider_buildRequestBody(t *testing.T) {
}
}

func TestAlertProvider_pagerDutySeverity(t *testing.T) {
alertProvider := new(AlertProvider)
scenarios := []struct {
result *core.Result
actualSeverityString, expectedSeverityString string
}{
{result: &core.Result{Severity: core.Severity{}}, actualSeverityString: "critical", expectedSeverityString: "critical"},
{result: &core.Result{Severity: core.Severity{Low: true, Medium: true, High: true, Critical: true}}, actualSeverityString: "critical", expectedSeverityString: "critical"},
{result: &core.Result{Severity: core.Severity{Low: true, Medium: true, High: true}}, actualSeverityString: "error", expectedSeverityString: "error"},
{result: &core.Result{Severity: core.Severity{Low: true, Medium: true}}, actualSeverityString: "warning", expectedSeverityString: "warning"},
{result: &core.Result{Severity: core.Severity{Low: true}}, actualSeverityString: "info", expectedSeverityString: "info"},
}

for _, scenario := range scenarios {
if alertProvider.pagerDutySeverity(scenario.result) != scenario.expectedSeverityString {
t.Errorf("expected %v, got %v", scenario.expectedSeverityString, scenario.actualSeverityString)
}
}
}

func TestAlertProvider_getIntegrationKeyForGroup(t *testing.T) {
scenarios := []struct {
Name string
Expand Down
31 changes: 29 additions & 2 deletions core/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package core
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -106,7 +107,8 @@ func (c Condition) Validate() error {

// evaluate the Condition with the Result of the health check
func (c Condition) evaluate(result *Result, dontResolveFailedConditions bool) bool {
condition := string(c)
severityStatus := None
severityByCondition, condition := c.sanitizeSeverityCondition()
success := false
conditionToDisplay := condition
if strings.Contains(condition, " == ") {
Expand Down Expand Up @@ -151,11 +153,36 @@ func (c Condition) evaluate(result *Result, dontResolveFailedConditions bool) bo
}
if !success {
//log.Printf("[Condition][evaluate] Condition '%s' did not succeed because '%s' is false", condition, condition)
severityStatus = severityByCondition
}
result.ConditionResults = append(result.ConditionResults, &ConditionResult{Condition: conditionToDisplay, Success: success})
result.ConditionResults = append(result.ConditionResults, &ConditionResult{Condition: conditionToDisplay, Success: success, SeverityStatus: severityStatus})
return success
}

// Extracts severity status from condition.
// Returns separated SeverityStatus and condition
func (c Condition) sanitizeSeverityCondition() (SeverityStatus, string) {
severityStatus, complexCondition := Critical, string(c)
regex, _ := regexp.Compile(`(Low|Medium|High|Critical) :: (.+)`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The regexp could be case-insensitive.

Suggested change
regex, _ := regexp.Compile(`(Low|Medium|High|Critical) :: (.+)`)
regex, _ := regexp.Compile(`(?i)(Low|Medium|High|Critical) :: (.+)`)

matchedStringsSlice := regex.FindStringSubmatch(complexCondition)
if len(matchedStringsSlice) == 0 {
return severityStatus, complexCondition
}

switch foundSeverityStatus := matchedStringsSlice[1]; {
case foundSeverityStatus == "Low":
severityStatus = Low
case foundSeverityStatus == "Medium":
severityStatus = Medium
case foundSeverityStatus == "High":
severityStatus = High
case foundSeverityStatus == "Critical":
severityStatus = Critical
}

return severityStatus, matchedStringsSlice[2]
}

// hasBodyPlaceholder checks whether the condition has a BodyPlaceholder
// Used for determining whether the response body should be read or not
func (c Condition) hasBodyPlaceholder() bool {
Expand Down
3 changes: 3 additions & 0 deletions core/condition_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ type ConditionResult struct {

// Success whether the condition was met (successful) or not (failed)
Success bool `json:"success"`

// Severity of condition, evaluated during failure case only
SeverityStatus SeverityStatus `json:"-"`
}
Loading