Skip to content

Commit

Permalink
feat: Support looking up values into headers and body
Browse files Browse the repository at this point in the history
  • Loading branch information
adityathebe committed Oct 13, 2023
1 parent 4536a5e commit c08ee99
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 528 deletions.
101 changes: 73 additions & 28 deletions checks/http.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package checks

import (
"fmt"
"net/url"
"strconv"
"strings"
"time"

"github.com/PaesslerAG/jsonpath"
"github.com/flanksource/canary-checker/api/context"
"github.com/flanksource/commons/http"
"github.com/flanksource/commons/http/middlewares"
"github.com/flanksource/commons/text"
"github.com/flanksource/duty/models"
"github.com/pkg/errors"
Expand All @@ -16,7 +20,6 @@ import (

v1 "github.com/flanksource/canary-checker/api/v1"
"github.com/flanksource/canary-checker/pkg"
"github.com/flanksource/canary-checker/pkg/http"
"github.com/flanksource/canary-checker/pkg/metrics"
"github.com/flanksource/canary-checker/pkg/utils"
)
Expand Down Expand Up @@ -61,28 +64,36 @@ func (c *HTTPChecker) Run(ctx *context.Context) pkg.Results {
return results
}

func (c *HTTPChecker) configure(req *http.HTTPRequest, ctx *context.Context, check v1.HTTPCheck, connection *models.Connection) error {
func (c *HTTPChecker) generateHTTPRequest(ctx *context.Context, check v1.HTTPCheck, connection *models.Connection) (*http.Request, error) {
client := http.NewClient()

for _, header := range check.Headers {
value, err := ctx.GetEnvValueFromCache(header)
if err != nil {
return errors.WithMessagef(err, "failed getting header: %v", header)
return nil, errors.WithMessagef(err, "failed getting header: %v", header)
}
req.Header(header.Name, value)

client.Header(header.Name, value)
}

if connection.Username != "" || connection.Password != "" {
req.Auth(connection.Username, connection.Password)
client.Auth(connection.Username, connection.Password)
}

req.NTLM(check.NTLM)
req.NTLMv2(check.NTLMv2)
client.NTLM(check.NTLM)
client.NTLMV2(check.NTLMv2)

if check.ThresholdMillis > 0 {
req.Timeout(time.Duration(check.ThresholdMillis) * time.Millisecond)
client.Timeout(time.Duration(check.ThresholdMillis) * time.Millisecond)
}

req.Trace(ctx.IsTrace()).Debug(ctx.IsDebug())
return nil
// TODO: Add finer controls over tracing to the canary
if ctx.IsTrace() {
tracedTransport := middlewares.NewTracedTransport().TraceAll(true).MaxBodyLength(512)
client.Use(tracedTransport.RoundTripper)
}

return client.R(ctx), nil
}

func truncate(text string, max int) string {
Expand Down Expand Up @@ -137,17 +148,27 @@ func (c *HTTPChecker) Check(ctx *context.Context, extConfig external.Check) pkg.
}
}

req := http.NewRequest(connection.URL).Method(check.GetMethod())

if err := c.configure(req, ctx, check, connection); err != nil {
request, err := c.generateHTTPRequest(ctx, check, connection)
if err != nil {
return results.ErrorMessage(err)
}

if body != "" {
if err := request.Body(body); err != nil {
return results.ErrorMessage(err)
}
}

start := time.Now()

resp := req.Do(body)
response, err := request.Do(check.GetMethod(), connection.URL)
if err != nil {
return results.ErrorMessage(err)
}

elapsed := time.Since(start)
status := resp.GetStatusCode()
status := response.StatusCode

result.AddMetric(pkg.Metric{
Name: "response_code",
Type: metrics.CounterType,
Expand All @@ -156,30 +177,31 @@ func (c *HTTPChecker) Check(ctx *context.Context, extConfig external.Check) pkg.
"url": check.URL,
},
})

result.Duration = elapsed.Milliseconds()
responseStatus.WithLabelValues(strconv.Itoa(status), statusCodeToClass(status), check.URL).Inc()
age := resp.GetSSLAge()
age := response.GetSSLAge()
if age != nil {
sslExpiration.WithLabelValues(check.URL).Set(age.Hours() * 24)
}

body, _ = resp.AsString()
body, _ = response.AsString()

data := map[string]interface{}{
"code": status,
"headers": resp.GetHeaders(),
"headers": response.Header,
"elapsed": time.Since(start),
"content": body,
"sslAge": utils.Deref(age),
"json": make(map[string]any),
}

if resp.IsJSON() {
json, err := resp.AsJSON()
if response.IsJSON() {
json, err := response.AsJSON()
if err == nil {
data["json"] = json.Value
data["json"] = json
if check.ResponseJSONContent != nil && check.ResponseJSONContent.Path != "" {
err := resp.CheckJSONContent(json.Value, check.ResponseJSONContent)
err := checkJSONContent(json, check.ResponseJSONContent)
if err != nil {
return results.ErrorMessage(err)
}
Expand All @@ -193,11 +215,7 @@ func (c *HTTPChecker) Check(ctx *context.Context, extConfig external.Check) pkg.

result.AddData(data)

if status == -1 {
return results.Failf("%v", truncate(resp.Error.Error(), 500))
}

if ok := resp.IsOK(check.ResponseCodes...); !ok {
if ok := response.IsOK(check.ResponseCodes...); !ok {
return results.Failf("response code invalid %d != %v", status, check.ResponseCodes)
}

Expand All @@ -209,14 +227,15 @@ func (c *HTTPChecker) Check(ctx *context.Context, extConfig external.Check) pkg.
return results.Failf("expected %v, found %v", check.ResponseContent, truncate(body, 100))
}

if req.URL.Scheme == "https" && check.MaxSSLExpiry > 0 {
if check.MaxSSLExpiry > 0 {
if age == nil {
return results.Failf("No certificate found to check age")
}
if *age < time.Duration(check.MaxSSLExpiry)*time.Hour*24 {
return results.Failf("SSL certificate expires soon %s > %d", utils.Age(*age), check.MaxSSLExpiry)
}
}

return results
}

Expand All @@ -235,3 +254,29 @@ func statusCodeToClass(statusCode int) string {
return "unknown"
}
}

func checkJSONContent(jsonContent map[string]any, jsonCheck *v1.JSONCheck) error {
if jsonCheck == nil {
return nil
}

jsonResult, err := jsonpath.Get(jsonCheck.Path, jsonContent)
if err != nil {
return fmt.Errorf("error getting jsonPath: %w", err)
}

switch s := jsonResult.(type) {
case string:
if s != jsonCheck.Value {
return fmt.Errorf("%v not equal to %v", s, jsonCheck.Value)
}
case fmt.Stringer:
if s.String() != jsonCheck.Value {
return fmt.Errorf("%v not equal to %v", s.String(), jsonCheck.Value)
}
default:
return fmt.Errorf("json response could not be parsed back to string")
}

return nil
}
32 changes: 16 additions & 16 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ require (
github.com/eko/gocache/store/bigcache/v4 v4.2.1
github.com/elastic/go-elasticsearch/v8 v8.10.0
github.com/fergusstrange/embedded-postgres v1.24.0
github.com/flanksource/commons v1.12.0
github.com/flanksource/commons v1.14.1
github.com/flanksource/duty v1.0.191
github.com/flanksource/gomplate/v3 v3.20.16
github.com/flanksource/is-healthy v0.0.0-20231003215854-76c51e3a3ff7
Expand Down Expand Up @@ -66,10 +66,10 @@ require (
github.com/vadimi/go-http-ntlm/v2 v2.4.1
go.mongodb.org/mongo-driver v1.12.1
golang.org/x/crypto v0.14.0
golang.org/x/net v0.15.0
golang.org/x/net v0.17.0
golang.org/x/sync v0.4.0
google.golang.org/api v0.145.0
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97
google.golang.org/api v0.147.0
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a
gopkg.in/flanksource/yaml.v3 v3.2.3
gorm.io/gorm v1.25.4
gorm.io/plugin/prometheus v0.0.0-20230504115745-1aec2356381b
Expand All @@ -83,9 +83,9 @@ require (
require (
ariga.io/atlas v0.14.2 // indirect
cloud.google.com/go v0.110.8 // indirect
cloud.google.com/go/compute v1.23.0 // indirect
cloud.google.com/go/compute v1.23.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.2 // indirect
cloud.google.com/go/iam v1.1.3 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
Expand All @@ -97,7 +97,7 @@ require (
github.com/antonmedv/expr v1.15.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go v1.45.23 // indirect
github.com/aws/aws-sdk-go v1.45.25 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect
Expand Down Expand Up @@ -154,7 +154,7 @@ require (
github.com/google/btree v1.1.2 // indirect
github.com/google/cel-go v0.18.1 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect
github.com/google/s2a-go v0.1.7 // indirect
Expand All @@ -167,7 +167,7 @@ require (
github.com/hairyhenderson/toml v0.4.2-0.20210923231440-40456b8e66cf // indirect
github.com/hairyhenderson/yaml v0.0.0-20220618171115-2d35fca545ce // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-getter v1.7.2 // indirect
github.com/hashicorp/go-getter v1.7.3 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hcl/v2 v2.18.0 // indirect
Expand Down Expand Up @@ -239,19 +239,19 @@ require (
go.starlark.net v0.0.0-20230925163745-10651d5192ab // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/exp v0.0.0-20231005195138-3e424a577f31 // indirect
golang.org/x/oauth2 v0.12.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/oauth2 v0.13.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.13.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
golang.org/x/tools v0.14.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect
google.golang.org/grpc v1.58.2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a // indirect
google.golang.org/grpc v1.58.3 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
Expand Down
Loading

0 comments on commit c08ee99

Please sign in to comment.