Skip to content

Commit

Permalink
Feature/Severity status
Browse files Browse the repository at this point in the history
* Added ConditionResult.Severity
* Added Result.Severity
* Added Result.SeverityStatus
* Added Result#determineSeverityStatus, tests
* Added SeverityStatus type
* Added Condition#sanitizeSeverityCondition, tests
* Updated Condition#evaluate, tests
* Updated Endpoint#EvaluateHealth, tests
* Updated storage adapters
* Updated Endpoint component (vue)
* Recompiled assets
* Updated readme
* Added critical severity, add result severity to pagerduty
  • Loading branch information
bestwebua committed Jul 16, 2023
1 parent a725e7e commit 9d04469
Show file tree
Hide file tree
Showing 16 changed files with 730 additions and 409 deletions.
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 @@ -272,7 +273,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 @@ -285,6 +285,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 |
|:----------------------------|
|`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) :: (.+)`)
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

0 comments on commit 9d04469

Please sign in to comment.