Merge branch 'master' into add-dns-feature

# Conflicts:
#	core/condition.go
This commit is contained in:
cemturker 2020-11-18 18:53:00 +01:00
commit cef94299e5
8 changed files with 86 additions and 32 deletions

View File

@ -1,3 +1,5 @@
example
Dockerfile
.github
.idea
.git

View File

@ -1,9 +1,9 @@
# Build the go application into a binary
FROM golang:alpine as builder
WORKDIR /app
ADD . ./
RUN CGO_ENABLED=0 GOOS=linux go build -mod vendor -a -installsuffix cgo -o gatus .
RUN apk --update add ca-certificates
WORKDIR /app
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -mod vendor -a -installsuffix cgo -o gatus .
# Run Tests inside docker image if you don't have a configured go environment
#RUN apk update && apk add --virtual build-dependencies build-base gcc
@ -17,4 +17,4 @@ COPY --from=builder /app/static static/
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
ENV PORT=8080
EXPOSE ${PORT}
ENTRYPOINT ["/gatus"]
ENTRYPOINT ["/gatus"]

View File

@ -159,6 +159,7 @@ Here are some examples of conditions you can use:
| `len([BODY].data) < 5` | Array at JSONPath `$.data` has less than 5 elements | `{"data":[{"id":1}]}` | |
| `len([BODY].name) == 8` | String at JSONPath `$.name` has a length of 8 | `{"name":"john.doe"}` | `{"name":"bob"}` |
| `[BODY].name == pat(john*)` | String at JSONPath `$.name` matches pattern `john*` | `{"name":"john.doe"}` | `{"name":"bob"}` |
| `[CERTIFICATE_EXPIRATION] > 48h` | Certificate expiration is more than 48h away | 49h, 50h, 123h | 1h, 24h, ... |
#### Placeholders
@ -171,7 +172,7 @@ Here are some examples of conditions you can use:
| `[IP]` | Resolves into the IP of the target host | 192.168.0.232
| `[BODY]` | Resolves into the response body. Supports JSONPath. | `{"name":"john.doe"}`
| `[CONNECTED]` | Resolves into whether a connection could be established | `true`
| `[CERTIFICATE_EXPIRATION]` | Resolves into the duration before certificate expiration, in ms | 4461677039, 0 (if not using HTTPS)
| `[CERTIFICATE_EXPIRATION]` | Resolves into the duration before certificate expiration | `24h`, `48h`, 0 (if not using HTTPS)
#### Functions

View File

@ -2,6 +2,7 @@ package slack
import (
"fmt"
"github.com/TwinProduction/gatus/alerting/provider/custom"
"github.com/TwinProduction/gatus/core"
)
@ -18,8 +19,7 @@ func (provider *AlertProvider) IsValid() bool {
// ToCustomAlertProvider converts the provider into a custom.AlertProvider
func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, alert *core.Alert, result *core.Result, resolved bool) *custom.AlertProvider {
var message string
var color string
var message, color, results string
if resolved {
message = fmt.Sprintf("An alert for *%s* has been resolved after passing successfully %d time(s) in a row", service.Name, alert.SuccessThreshold)
color = "#36A64F"
@ -27,7 +27,6 @@ func (provider *AlertProvider) ToCustomAlertProvider(service *core.Service, aler
message = fmt.Sprintf("An alert for *%s* has been triggered due to having failed %d time(s) in a row", service.Name, alert.FailureThreshold)
color = "#DD0000"
}
var results string
for _, conditionResult := range result.ConditionResults {
var prefix string
if conditionResult.Success {

View File

@ -1,9 +1,10 @@
package slack
import (
"github.com/TwinProduction/gatus/core"
"strings"
"testing"
"github.com/TwinProduction/gatus/core"
)
func TestAlertProvider_IsValid(t *testing.T) {

View File

@ -2,11 +2,13 @@ package core
import (
"fmt"
"github.com/TwinProduction/gatus/jsonpath"
"github.com/TwinProduction/gatus/pattern"
"log"
"strconv"
"strings"
"time"
"github.com/TwinProduction/gatus/jsonpath"
"github.com/TwinProduction/gatus/pattern"
)
const (
@ -15,30 +17,30 @@ const (
// Values that could replace the placeholder: 200, 404, 500, ...
StatusPlaceholder = "[STATUS]"
// IPPlaceHolder is a placeholder for an IP.
// IPPlaceholder is a placeholder for an IP.
//
// Values that could replace the placeholder: 127.0.0.1, 10.0.0.1, ...
IPPlaceHolder = "[IP]"
IPPlaceholder = "[IP]"
// DNSRCodePlaceHolder is a place holder for DNS_RCODE
//
// Values that could be NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP and REFUSED
DNSRCodePlaceHolder = "[DNS_RCODE]"
// ResponseTimePlaceHolder is a placeholder for the request response time, in milliseconds.
// ResponseTimePlaceholder is a placeholder for the request response time, in milliseconds.
//
// Values that could replace the placeholder: 1, 500, 1000, ...
ResponseTimePlaceHolder = "[RESPONSE_TIME]"
ResponseTimePlaceholder = "[RESPONSE_TIME]"
// BodyPlaceHolder is a placeholder for the body of the response
// BodyPlaceholder is a placeholder for the body of the response
//
// Values that could replace the placeholder: {}, {"data":{"name":"john"}}, ...
BodyPlaceHolder = "[BODY]"
BodyPlaceholder = "[BODY]"
// ConnectedPlaceHolder is a placeholder for whether a connection was successfully established.
// ConnectedPlaceholder is a placeholder for whether a connection was successfully established.
//
// Values that could replace the placeholder: true, false
ConnectedPlaceHolder = "[CONNECTED]"
ConnectedPlaceholder = "[CONNECTED]"
// CertificateExpirationPlaceholder is a placeholder for the duration before certificate expiration, in milliseconds.
//
@ -144,27 +146,27 @@ func sanitizeAndResolve(list []string, result *Result) []string {
switch strings.ToUpper(element) {
case StatusPlaceholder:
element = strconv.Itoa(result.HTTPStatus)
case IPPlaceHolder:
case IPPlaceholder:
element = result.IP
case ResponseTimePlaceHolder:
case ResponseTimePlaceholder:
element = strconv.Itoa(int(result.Duration.Milliseconds()))
case BodyPlaceholder:
element = body
case DNSRCodePlaceHolder:
element = result.DNSRCode
case BodyPlaceHolder:
element = body
case ConnectedPlaceHolder:
case ConnectedPlaceholder:
element = strconv.FormatBool(result.Connected)
case CertificateExpirationPlaceholder:
element = strconv.FormatInt(int64(result.CertificateExpiration.Milliseconds()), 10)
element = strconv.FormatInt(result.CertificateExpiration.Milliseconds(), 10)
default:
// if contains the BodyPlaceHolder, then evaluate json path
if strings.Contains(element, BodyPlaceHolder) {
// if contains the BodyPlaceholder, then evaluate json path
if strings.Contains(element, BodyPlaceholder) {
wantLength := false
if strings.HasPrefix(element, LengthFunctionPrefix) && strings.HasSuffix(element, FunctionSuffix) {
wantLength = true
element = strings.TrimSuffix(strings.TrimPrefix(element, LengthFunctionPrefix), FunctionSuffix)
}
resolvedElement, resolvedElementLength, err := jsonpath.Eval(strings.Replace(element, fmt.Sprintf("%s.", BodyPlaceHolder), "", 1), result.Body)
resolvedElement, resolvedElementLength, err := jsonpath.Eval(strings.Replace(element, fmt.Sprintf("%s.", BodyPlaceholder), "", 1), result.Body)
if err != nil {
if err.Error() != "unexpected end of JSON input" {
result.Errors = append(result.Errors, err.Error())
@ -192,7 +194,9 @@ func sanitizeAndResolveNumerical(list []string, result *Result) []int64 {
var sanitizedNumbers []int64
sanitizedList := sanitizeAndResolve(list, result)
for _, element := range sanitizedList {
if number, err := strconv.ParseInt(element, 10, 64); err != nil {
if duration, err := time.ParseDuration(element); duration != 0 && err == nil {
sanitizedNumbers = append(sanitizedNumbers, duration.Milliseconds())
} else if number, err := strconv.ParseInt(element, 10, 64); err != nil {
// Default to 0 if the string couldn't be converted to an integer
sanitizedNumbers = append(sanitizedNumbers, 0)
} else {

View File

@ -60,7 +60,27 @@ func TestCondition_evaluateWithResponseTimeUsingLessThan(t *testing.T) {
}
}
func TestCondition_evaluateWithResponseTimeUsingLessThanDuration(t *testing.T) {
condition := Condition("[RESPONSE_TIME] < 1s")
result := &Result{Duration: time.Millisecond * 50}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
}
}
func TestCondition_evaluateWithResponseTimeUsingLessThanInvalid(t *testing.T) {
condition := Condition("[RESPONSE_TIME] < potato")
result := &Result{Duration: time.Millisecond * 50}
condition.evaluate(result)
if result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have failed because the condition has an invalid numerical value that should've automatically resolved to 0", condition)
}
}
func TestCondition_evaluateWithResponseTimeUsingGreaterThan(t *testing.T) {
// Not exactly sure why you'd want to have a condition that checks if the response time is too fast,
// but hey, who am I to judge?
condition := Condition("[RESPONSE_TIME] > 500")
result := &Result{Duration: time.Millisecond * 750}
condition.evaluate(result)
@ -69,6 +89,15 @@ func TestCondition_evaluateWithResponseTimeUsingGreaterThan(t *testing.T) {
}
}
func TestCondition_evaluateWithResponseTimeUsingGreaterThanDuration(t *testing.T) {
condition := Condition("[RESPONSE_TIME] > 1s")
result := &Result{Duration: time.Second * 2}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
}
}
func TestCondition_evaluateWithResponseTimeUsingGreaterThanOrEqualTo(t *testing.T) {
condition := Condition("[RESPONSE_TIME] >= 500")
result := &Result{Duration: time.Millisecond * 500}
@ -320,7 +349,7 @@ func TestCondition_evaluateWithUnsetCertificateExpiration(t *testing.T) {
}
}
func TestCondition_evaluateWithCertificateExpirationGreaterThan(t *testing.T) {
func TestCondition_evaluateWithCertificateExpirationGreaterThanNumerical(t *testing.T) {
acceptable := (time.Hour * 24 * 28).Milliseconds()
condition := Condition("[CERTIFICATE_EXPIRATION] > " + strconv.FormatInt(acceptable, 10))
result := &Result{CertificateExpiration: time.Hour * 24 * 60}
@ -330,7 +359,7 @@ func TestCondition_evaluateWithCertificateExpirationGreaterThan(t *testing.T) {
}
}
func TestCondition_evaluateWithCertificateExpirationGreaterThanFailure(t *testing.T) {
func TestCondition_evaluateWithCertificateExpirationGreaterThanNumericalFailure(t *testing.T) {
acceptable := (time.Hour * 24 * 28).Milliseconds()
condition := Condition("[CERTIFICATE_EXPIRATION] > " + strconv.FormatInt(acceptable, 10))
result := &Result{CertificateExpiration: time.Hour * 24 * 14}
@ -339,3 +368,21 @@ func TestCondition_evaluateWithCertificateExpirationGreaterThanFailure(t *testin
t.Errorf("Condition '%s' should have been a failure", condition)
}
}
func TestCondition_evaluateWithCertificateExpirationGreaterThanDuration(t *testing.T) {
condition := Condition("[CERTIFICATE_EXPIRATION] > 12h")
result := &Result{CertificateExpiration: 24 * time.Hour}
condition.evaluate(result)
if !result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a success", condition)
}
}
func TestCondition_evaluateWithCertificateExpirationGreaterThanDurationFailure(t *testing.T) {
condition := Condition("[CERTIFICATE_EXPIRATION] > 48h")
result := &Result{CertificateExpiration: 24 * time.Hour}
condition.evaluate(result)
if result.ConditionResults[0].Success {
t.Errorf("Condition '%s' should have been a failure", condition)
}
}

View File

@ -184,7 +184,7 @@ func (service *Service) call(result *Result) {
result.Errors = append(result.Errors, err.Error())
return
}
if response.TLS != nil {
if response.TLS != nil && len(response.TLS.PeerCertificates) > 0 {
certificate := response.TLS.PeerCertificates[0]
result.CertificateExpiration = certificate.NotAfter.Sub(time.Now())
}