Add support for headers, method, body and json path with arrays
This commit is contained in:
51
core/condition.go
Normal file
51
core/condition.go
Normal file
@ -0,0 +1,51 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Condition string
|
||||
|
||||
func (c *Condition) evaluate(result *Result) bool {
|
||||
condition := string(*c)
|
||||
success := false
|
||||
var resolvedCondition string
|
||||
if strings.Contains(condition, "==") {
|
||||
parts := sanitizeAndResolve(strings.Split(condition, "=="), result)
|
||||
success = parts[0] == parts[1]
|
||||
resolvedCondition = fmt.Sprintf("%v == %v", parts[0], parts[1])
|
||||
} else if strings.Contains(condition, "!=") {
|
||||
parts := sanitizeAndResolve(strings.Split(condition, "!="), result)
|
||||
success = parts[0] != parts[1]
|
||||
resolvedCondition = fmt.Sprintf("%v != %v", parts[0], parts[1])
|
||||
} else if strings.Contains(condition, "<=") {
|
||||
parts := sanitizeAndResolveNumerical(strings.Split(condition, "<="), result)
|
||||
success = parts[0] <= parts[1]
|
||||
resolvedCondition = fmt.Sprintf("%v <= %v", parts[0], parts[1])
|
||||
} else if strings.Contains(condition, ">=") {
|
||||
parts := sanitizeAndResolveNumerical(strings.Split(condition, ">="), result)
|
||||
success = parts[0] >= parts[1]
|
||||
resolvedCondition = fmt.Sprintf("%v >= %v", parts[0], parts[1])
|
||||
} else if strings.Contains(condition, ">") {
|
||||
parts := sanitizeAndResolveNumerical(strings.Split(condition, ">"), result)
|
||||
success = parts[0] > parts[1]
|
||||
resolvedCondition = fmt.Sprintf("%v > %v", parts[0], parts[1])
|
||||
} else if strings.Contains(condition, "<") {
|
||||
parts := sanitizeAndResolveNumerical(strings.Split(condition, "<"), result)
|
||||
success = parts[0] < parts[1]
|
||||
resolvedCondition = fmt.Sprintf("%v < %v", parts[0], parts[1])
|
||||
} else {
|
||||
result.Errors = append(result.Errors, fmt.Sprintf("invalid condition '%s' has been provided", condition))
|
||||
return false
|
||||
}
|
||||
conditionToDisplay := condition
|
||||
// If the condition isn't a success, return the resolved condition
|
||||
if !success {
|
||||
log.Printf("[Condition][evaluate] Condition '%s' did not succeed because '%s' is false", condition, resolvedCondition)
|
||||
conditionToDisplay = resolvedCondition
|
||||
}
|
||||
result.ConditionResults = append(result.ConditionResults, &ConditionResult{Condition: conditionToDisplay, Success: success})
|
||||
return success
|
||||
}
|
@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEvaluateWithIp(t *testing.T) {
|
||||
func TestCondition_evaluateWithIp(t *testing.T) {
|
||||
condition := Condition("[IP] == 127.0.0.1")
|
||||
result := &Result{Ip: "127.0.0.1"}
|
||||
condition.evaluate(result)
|
||||
@ -14,7 +14,7 @@ func TestEvaluateWithIp(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithStatus(t *testing.T) {
|
||||
func TestCondition_evaluateWithStatus(t *testing.T) {
|
||||
condition := Condition("[STATUS] == 201")
|
||||
result := &Result{HttpStatus: 201}
|
||||
condition.evaluate(result)
|
||||
@ -23,7 +23,7 @@ func TestEvaluateWithStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithStatusFailure(t *testing.T) {
|
||||
func TestCondition_evaluateWithStatusFailure(t *testing.T) {
|
||||
condition := Condition("[STATUS] == 200")
|
||||
result := &Result{HttpStatus: 500}
|
||||
condition.evaluate(result)
|
||||
@ -32,7 +32,7 @@ func TestEvaluateWithStatusFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithStatusUsingLessThan(t *testing.T) {
|
||||
func TestCondition_evaluateWithStatusUsingLessThan(t *testing.T) {
|
||||
condition := Condition("[STATUS] < 300")
|
||||
result := &Result{HttpStatus: 201}
|
||||
condition.evaluate(result)
|
||||
@ -41,7 +41,7 @@ func TestEvaluateWithStatusUsingLessThan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithStatusFailureUsingLessThan(t *testing.T) {
|
||||
func TestCondition_evaluateWithStatusFailureUsingLessThan(t *testing.T) {
|
||||
condition := Condition("[STATUS] < 300")
|
||||
result := &Result{HttpStatus: 404}
|
||||
condition.evaluate(result)
|
||||
@ -50,7 +50,7 @@ func TestEvaluateWithStatusFailureUsingLessThan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithResponseTimeUsingLessThan(t *testing.T) {
|
||||
func TestCondition_evaluateWithResponseTimeUsingLessThan(t *testing.T) {
|
||||
condition := Condition("[RESPONSE_TIME] < 500")
|
||||
result := &Result{Duration: time.Millisecond * 50}
|
||||
condition.evaluate(result)
|
||||
@ -59,7 +59,7 @@ func TestEvaluateWithResponseTimeUsingLessThan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithResponseTimeUsingGreaterThan(t *testing.T) {
|
||||
func TestCondition_evaluateWithResponseTimeUsingGreaterThan(t *testing.T) {
|
||||
condition := Condition("[RESPONSE_TIME] > 500")
|
||||
result := &Result{Duration: time.Millisecond * 750}
|
||||
condition.evaluate(result)
|
||||
@ -68,7 +68,7 @@ func TestEvaluateWithResponseTimeUsingGreaterThan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithResponseTimeUsingGreaterThanOrEqualTo(t *testing.T) {
|
||||
func TestCondition_evaluateWithResponseTimeUsingGreaterThanOrEqualTo(t *testing.T) {
|
||||
condition := Condition("[RESPONSE_TIME] >= 500")
|
||||
result := &Result{Duration: time.Millisecond * 500}
|
||||
condition.evaluate(result)
|
||||
@ -77,7 +77,7 @@ func TestEvaluateWithResponseTimeUsingGreaterThanOrEqualTo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithResponseTimeUsingLessThanOrEqualTo(t *testing.T) {
|
||||
func TestCondition_evaluateWithResponseTimeUsingLessThanOrEqualTo(t *testing.T) {
|
||||
condition := Condition("[RESPONSE_TIME] <= 500")
|
||||
result := &Result{Duration: time.Millisecond * 500}
|
||||
condition.evaluate(result)
|
||||
@ -86,7 +86,7 @@ func TestEvaluateWithResponseTimeUsingLessThanOrEqualTo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithBody(t *testing.T) {
|
||||
func TestCondition_evaluateWithBody(t *testing.T) {
|
||||
condition := Condition("[BODY] == test")
|
||||
result := &Result{Body: []byte("test")}
|
||||
condition.evaluate(result)
|
||||
@ -95,7 +95,7 @@ func TestEvaluateWithBody(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithBodyJsonPath(t *testing.T) {
|
||||
func TestCondition_evaluateWithBodyJsonPath(t *testing.T) {
|
||||
condition := Condition("[BODY].status == UP")
|
||||
result := &Result{Body: []byte("{\"status\":\"UP\"}")}
|
||||
condition.evaluate(result)
|
||||
@ -104,7 +104,7 @@ func TestEvaluateWithBodyJsonPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithBodyJsonPathComplex(t *testing.T) {
|
||||
func TestCondition_evaluateWithBodyJsonPathComplex(t *testing.T) {
|
||||
condition := Condition("[BODY].data.name == john")
|
||||
result := &Result{Body: []byte("{\"data\": {\"id\": 1, \"name\": \"john\"}}")}
|
||||
condition.evaluate(result)
|
||||
@ -113,7 +113,7 @@ func TestEvaluateWithBodyJsonPathComplex(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithBodyJsonPathComplexInt(t *testing.T) {
|
||||
func TestCondition_evaluateWithBodyJsonPathLongInt(t *testing.T) {
|
||||
condition := Condition("[BODY].data.id == 1")
|
||||
result := &Result{Body: []byte("{\"data\": {\"id\": 1}}")}
|
||||
condition.evaluate(result)
|
||||
@ -122,7 +122,16 @@ func TestEvaluateWithBodyJsonPathComplexInt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithBodyJsonPathComplexIntUsingGreaterThan(t *testing.T) {
|
||||
func TestCondition_evaluateWithBodyJsonPathComplexInt(t *testing.T) {
|
||||
condition := Condition("[BODY].data[1].id == 2")
|
||||
result := &Result{Body: []byte("{\"data\": [{\"id\": 1}, {\"id\": 2}, {\"id\": 3}]}")}
|
||||
condition.evaluate(result)
|
||||
if !result.ConditionResults[0].Success {
|
||||
t.Errorf("Condition '%s' should have been a success", condition)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCondition_evaluateWithBodyJsonPathComplexIntUsingGreaterThan(t *testing.T) {
|
||||
condition := Condition("[BODY].data.id > 0")
|
||||
result := &Result{Body: []byte("{\"data\": {\"id\": 1}}")}
|
||||
condition.evaluate(result)
|
||||
@ -131,7 +140,7 @@ func TestEvaluateWithBodyJsonPathComplexIntUsingGreaterThan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithBodyJsonPathComplexIntFailureUsingGreaterThan(t *testing.T) {
|
||||
func TestCondition_evaluateWithBodyJsonPathComplexIntFailureUsingGreaterThan(t *testing.T) {
|
||||
condition := Condition("[BODY].data.id > 5")
|
||||
result := &Result{Body: []byte("{\"data\": {\"id\": 1}}")}
|
||||
condition.evaluate(result)
|
||||
@ -140,7 +149,7 @@ func TestEvaluateWithBodyJsonPathComplexIntFailureUsingGreaterThan(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithBodyJsonPathComplexIntUsingLessThan(t *testing.T) {
|
||||
func TestCondition_evaluateWithBodyJsonPathComplexIntUsingLessThan(t *testing.T) {
|
||||
condition := Condition("[BODY].data.id < 5")
|
||||
result := &Result{Body: []byte("{\"data\": {\"id\": 2}}")}
|
||||
condition.evaluate(result)
|
||||
@ -149,7 +158,7 @@ func TestEvaluateWithBodyJsonPathComplexIntUsingLessThan(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateWithBodyJsonPathComplexIntFailureUsingLessThan(t *testing.T) {
|
||||
func TestCondition_evaluateWithBodyJsonPathComplexIntFailureUsingLessThan(t *testing.T) {
|
||||
condition := Condition("[BODY].data.id < 5")
|
||||
result := &Result{Body: []byte("{\"data\": {\"id\": 10}}")}
|
||||
condition.evaluate(result)
|
||||
@ -157,35 +166,3 @@ func TestEvaluateWithBodyJsonPathComplexIntFailureUsingLessThan(t *testing.T) {
|
||||
t.Errorf("Condition '%s' should have been a failure", condition)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationEvaluateConditions(t *testing.T) {
|
||||
condition := Condition("[STATUS] == 200")
|
||||
service := Service{
|
||||
Name: "TwiNNatioN",
|
||||
Url: "https://twinnation.org/health",
|
||||
Conditions: []*Condition{&condition},
|
||||
}
|
||||
result := service.EvaluateConditions()
|
||||
if !result.ConditionResults[0].Success {
|
||||
t.Errorf("Condition '%s' should have been a success", condition)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Error("Because all conditions passed, this should have been a success")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationEvaluateConditionsWithFailure(t *testing.T) {
|
||||
condition := Condition("[STATUS] == 500")
|
||||
service := Service{
|
||||
Name: "TwiNNatioN",
|
||||
Url: "https://twinnation.org/health",
|
||||
Conditions: []*Condition{&condition},
|
||||
}
|
||||
result := service.EvaluateConditions()
|
||||
if result.ConditionResults[0].Success {
|
||||
t.Errorf("Condition '%s' should have been a failure", condition)
|
||||
}
|
||||
if result.Success {
|
||||
t.Error("Because one of the conditions failed, success should have been false")
|
||||
}
|
||||
}
|
95
core/service.go
Normal file
95
core/service.go
Normal file
@ -0,0 +1,95 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TwinProduction/gatus/client"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
Name string `yaml:"name"`
|
||||
Interval time.Duration `yaml:"interval,omitempty"`
|
||||
Url string `yaml:"url"`
|
||||
Method string `yaml:"method,omitempty"`
|
||||
Body string `yaml:"body,omitempty"`
|
||||
Headers map[string]string `yaml:"headers"`
|
||||
Conditions []*Condition `yaml:"conditions"`
|
||||
}
|
||||
|
||||
func (service *Service) Validate() {
|
||||
// Set default values
|
||||
if service.Interval == 0 {
|
||||
service.Interval = 10 * time.Second
|
||||
}
|
||||
if len(service.Method) == 0 {
|
||||
service.Method = http.MethodGet
|
||||
}
|
||||
|
||||
// Make sure that the request can be created
|
||||
_, err := http.NewRequest(service.Method, service.Url, bytes.NewBuffer([]byte(service.Body)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (service *Service) EvaluateConditions() *Result {
|
||||
result := &Result{Success: true, Errors: []string{}}
|
||||
service.getIp(result)
|
||||
if len(result.Errors) == 0 {
|
||||
service.call(result)
|
||||
} else {
|
||||
result.Success = false
|
||||
}
|
||||
for _, condition := range service.Conditions {
|
||||
success := condition.evaluate(result)
|
||||
if !success {
|
||||
result.Success = false
|
||||
}
|
||||
}
|
||||
result.Timestamp = time.Now()
|
||||
return result
|
||||
}
|
||||
|
||||
func (service *Service) getIp(result *Result) {
|
||||
urlObject, err := url.Parse(service.Url)
|
||||
if err != nil {
|
||||
result.Errors = append(result.Errors, err.Error())
|
||||
return
|
||||
}
|
||||
result.Hostname = urlObject.Hostname()
|
||||
ips, err := net.LookupIP(urlObject.Hostname())
|
||||
if err != nil {
|
||||
result.Errors = append(result.Errors, err.Error())
|
||||
return
|
||||
}
|
||||
result.Ip = ips[0].String()
|
||||
}
|
||||
|
||||
func (service *Service) call(result *Result) {
|
||||
request := service.buildRequest()
|
||||
startTime := time.Now()
|
||||
response, err := client.GetHttpClient().Do(request)
|
||||
if err != nil {
|
||||
result.Duration = time.Since(startTime)
|
||||
result.Errors = append(result.Errors, err.Error())
|
||||
return
|
||||
}
|
||||
result.Duration = time.Since(startTime)
|
||||
result.HttpStatus = response.StatusCode
|
||||
result.Body, err = ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
result.Errors = append(result.Errors, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (service *Service) buildRequest() *http.Request {
|
||||
request, _ := http.NewRequest(service.Method, service.Url, bytes.NewBuffer([]byte(service.Body)))
|
||||
for k, v := range service.Headers {
|
||||
request.Header.Set(k, v)
|
||||
}
|
||||
return request
|
||||
}
|
37
core/service_test.go
Normal file
37
core/service_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntegrationEvaluateConditions(t *testing.T) {
|
||||
condition := Condition("[STATUS] == 200")
|
||||
service := Service{
|
||||
Name: "TwiNNatioN",
|
||||
Url: "https://twinnation.org/health",
|
||||
Conditions: []*Condition{&condition},
|
||||
}
|
||||
result := service.EvaluateConditions()
|
||||
if !result.ConditionResults[0].Success {
|
||||
t.Errorf("Condition '%s' should have been a success", condition)
|
||||
}
|
||||
if !result.Success {
|
||||
t.Error("Because all conditions passed, this should have been a success")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationEvaluateConditionsWithFailure(t *testing.T) {
|
||||
condition := Condition("[STATUS] == 500")
|
||||
service := Service{
|
||||
Name: "TwiNNatioN",
|
||||
Url: "https://twinnation.org/health",
|
||||
Conditions: []*Condition{&condition},
|
||||
}
|
||||
result := service.EvaluateConditions()
|
||||
if result.ConditionResults[0].Success {
|
||||
t.Errorf("Condition '%s' should have been a failure", condition)
|
||||
}
|
||||
if result.Success {
|
||||
t.Error("Because one of the conditions failed, success should have been false")
|
||||
}
|
||||
}
|
147
core/types.go
147
core/types.go
@ -1,24 +1,9 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TwinProduction/gatus/jsonpath"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
StatusPlaceholder = "[STATUS]"
|
||||
IPPlaceHolder = "[IP]"
|
||||
ResponseTimePlaceHolder = "[RESPONSE_TIME]"
|
||||
BodyPlaceHolder = "[BODY]"
|
||||
)
|
||||
|
||||
type HealthStatus struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
@ -36,135 +21,7 @@ type Result struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
Name string `yaml:"name"`
|
||||
Url string `yaml:"url"`
|
||||
Interval time.Duration `yaml:"interval,omitempty"`
|
||||
Conditions []*Condition `yaml:"conditions"`
|
||||
}
|
||||
|
||||
func (service *Service) getIp(result *Result) {
|
||||
urlObject, err := url.Parse(service.Url)
|
||||
if err != nil {
|
||||
result.Errors = append(result.Errors, err.Error())
|
||||
return
|
||||
}
|
||||
result.Hostname = urlObject.Hostname()
|
||||
ips, err := net.LookupIP(urlObject.Hostname())
|
||||
if err != nil {
|
||||
result.Errors = append(result.Errors, err.Error())
|
||||
return
|
||||
}
|
||||
result.Ip = ips[0].String()
|
||||
}
|
||||
|
||||
func (service *Service) call(result *Result) {
|
||||
// TODO: re-use the same client instead of creating multiple clients
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 10,
|
||||
}
|
||||
startTime := time.Now()
|
||||
response, err := client.Get(service.Url)
|
||||
if err != nil {
|
||||
result.Errors = append(result.Errors, err.Error())
|
||||
return
|
||||
}
|
||||
result.Duration = time.Now().Sub(startTime)
|
||||
result.HttpStatus = response.StatusCode
|
||||
result.Body, err = ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
result.Errors = append(result.Errors, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (service *Service) EvaluateConditions() *Result {
|
||||
result := &Result{Success: true, Errors: []string{}}
|
||||
service.getIp(result)
|
||||
if len(result.Errors) == 0 {
|
||||
service.call(result)
|
||||
} else {
|
||||
result.Success = false
|
||||
}
|
||||
for _, condition := range service.Conditions {
|
||||
success := condition.evaluate(result)
|
||||
if !success {
|
||||
result.Success = false
|
||||
}
|
||||
}
|
||||
result.Timestamp = time.Now()
|
||||
return result
|
||||
}
|
||||
|
||||
type ConditionResult struct {
|
||||
Condition *Condition `json:"condition"`
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type Condition string
|
||||
|
||||
func (c *Condition) evaluate(result *Result) bool {
|
||||
condition := string(*c)
|
||||
success := false
|
||||
if strings.Contains(condition, "==") {
|
||||
parts := sanitizeAndResolve(strings.Split(condition, "=="), result)
|
||||
success = parts[0] == parts[1]
|
||||
} else if strings.Contains(condition, "!=") {
|
||||
parts := sanitizeAndResolve(strings.Split(condition, "!="), result)
|
||||
success = parts[0] != parts[1]
|
||||
} else if strings.Contains(condition, "<=") {
|
||||
parts := sanitizeAndResolveNumerical(strings.Split(condition, "<="), result)
|
||||
success = parts[0] <= parts[1]
|
||||
} else if strings.Contains(condition, ">=") {
|
||||
parts := sanitizeAndResolveNumerical(strings.Split(condition, ">="), result)
|
||||
success = parts[0] >= parts[1]
|
||||
} else if strings.Contains(condition, ">") {
|
||||
parts := sanitizeAndResolveNumerical(strings.Split(condition, ">"), result)
|
||||
success = parts[0] > parts[1]
|
||||
} else if strings.Contains(condition, "<") {
|
||||
parts := sanitizeAndResolveNumerical(strings.Split(condition, "<"), result)
|
||||
success = parts[0] < parts[1]
|
||||
} else {
|
||||
result.Errors = append(result.Errors, fmt.Sprintf("invalid condition '%s' has been provided", condition))
|
||||
return false
|
||||
}
|
||||
result.ConditionResults = append(result.ConditionResults, &ConditionResult{Condition: c, Success: success})
|
||||
return success
|
||||
}
|
||||
|
||||
func sanitizeAndResolve(list []string, result *Result) []string {
|
||||
var sanitizedList []string
|
||||
for _, element := range list {
|
||||
element = strings.TrimSpace(element)
|
||||
switch strings.ToUpper(element) {
|
||||
case StatusPlaceholder:
|
||||
element = strconv.Itoa(result.HttpStatus)
|
||||
case IPPlaceHolder:
|
||||
element = result.Ip
|
||||
case ResponseTimePlaceHolder:
|
||||
element = strconv.Itoa(int(result.Duration.Milliseconds()))
|
||||
case BodyPlaceHolder:
|
||||
element = string(result.Body)
|
||||
default:
|
||||
// if starts with BodyPlaceHolder, then do the jsonpath thingy
|
||||
if strings.HasPrefix(element, BodyPlaceHolder) {
|
||||
element = jsonpath.Eval(strings.Replace(element, fmt.Sprintf("%s.", BodyPlaceHolder), "", 1), result.Body)
|
||||
}
|
||||
}
|
||||
sanitizedList = append(sanitizedList, element)
|
||||
}
|
||||
return sanitizedList
|
||||
}
|
||||
|
||||
func sanitizeAndResolveNumerical(list []string, result *Result) []int {
|
||||
var sanitizedNumbers []int
|
||||
sanitizedList := sanitizeAndResolve(list, result)
|
||||
for _, element := range sanitizedList {
|
||||
if number, err := strconv.Atoi(element); err != nil {
|
||||
// Default to 0 if the string couldn't be converted to an integer
|
||||
sanitizedNumbers = append(sanitizedNumbers, 0)
|
||||
} else {
|
||||
sanitizedNumbers = append(sanitizedNumbers, number)
|
||||
}
|
||||
}
|
||||
return sanitizedNumbers
|
||||
Condition string `json:"condition"`
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
61
core/util.go
Normal file
61
core/util.go
Normal file
@ -0,0 +1,61 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TwinProduction/gatus/jsonpath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
StatusPlaceholder = "[STATUS]"
|
||||
IPPlaceHolder = "[IP]"
|
||||
ResponseTimePlaceHolder = "[RESPONSE_TIME]"
|
||||
BodyPlaceHolder = "[BODY]"
|
||||
|
||||
InvalidConditionElementSuffix = "(INVALID)"
|
||||
)
|
||||
|
||||
func sanitizeAndResolve(list []string, result *Result) []string {
|
||||
var sanitizedList []string
|
||||
for _, element := range list {
|
||||
element = strings.TrimSpace(element)
|
||||
switch strings.ToUpper(element) {
|
||||
case StatusPlaceholder:
|
||||
element = strconv.Itoa(result.HttpStatus)
|
||||
case IPPlaceHolder:
|
||||
element = result.Ip
|
||||
case ResponseTimePlaceHolder:
|
||||
element = strconv.Itoa(int(result.Duration.Milliseconds()))
|
||||
case BodyPlaceHolder:
|
||||
element = string(result.Body)
|
||||
default:
|
||||
// if starts with BodyPlaceHolder, then evaluate json path
|
||||
if strings.HasPrefix(element, BodyPlaceHolder) {
|
||||
resolvedElement, err := jsonpath.Eval(strings.Replace(element, fmt.Sprintf("%s.", BodyPlaceHolder), "", 1), result.Body)
|
||||
if err != nil {
|
||||
result.Errors = append(result.Errors, err.Error())
|
||||
element = fmt.Sprintf("%s %s", element, InvalidConditionElementSuffix)
|
||||
} else {
|
||||
element = resolvedElement
|
||||
}
|
||||
}
|
||||
}
|
||||
sanitizedList = append(sanitizedList, element)
|
||||
}
|
||||
return sanitizedList
|
||||
}
|
||||
|
||||
func sanitizeAndResolveNumerical(list []string, result *Result) []int {
|
||||
var sanitizedNumbers []int
|
||||
sanitizedList := sanitizeAndResolve(list, result)
|
||||
for _, element := range sanitizedList {
|
||||
if number, err := strconv.Atoi(element); err != nil {
|
||||
// Default to 0 if the string couldn't be converted to an integer
|
||||
sanitizedNumbers = append(sanitizedNumbers, 0)
|
||||
} else {
|
||||
sanitizedNumbers = append(sanitizedNumbers, number)
|
||||
}
|
||||
}
|
||||
return sanitizedNumbers
|
||||
}
|
Reference in New Issue
Block a user