@@ -18,6 +18,7 @@ import (
1818 "compress/gzip"
1919 "context"
2020 "crypto/tls"
21+ "encoding/json"
2122 "errors"
2223 "fmt"
2324 "io"
@@ -35,6 +36,7 @@ import (
3536 "time"
3637
3738 "github.com/andybalholm/brotli"
39+ "github.com/google/cel-go/cel"
3840 "github.com/prometheus/client_golang/prometheus"
3941 pconfig "github.com/prometheus/common/config"
4042 "github.com/prometheus/common/version"
@@ -64,6 +66,58 @@ func matchRegularExpressions(reader io.Reader, httpConfig config.HTTPProbe, logg
6466 return true
6567}
6668
69+ func matchCELExpressions (ctx context.Context , reader io.Reader , httpConfig config.HTTPProbe , logger * slog.Logger ) bool {
70+ body , err := io .ReadAll (reader )
71+ if err != nil {
72+ logger .Error ("Error reading HTTP body" , "err" , err )
73+ return false
74+ }
75+
76+ var bodyJSON any
77+ if err := json .Unmarshal (body , & bodyJSON ); err != nil {
78+ logger .Error ("Error unmarshalling HTTP body to JSON" , "err" , err )
79+ return false
80+ }
81+
82+ evalPayload := map [string ]interface {}{
83+ "body" : bodyJSON ,
84+ }
85+
86+ if httpConfig .FailIfBodyJsonMatchesCEL != nil {
87+ result , details , err := httpConfig .FailIfBodyJsonMatchesCEL .ContextEval (ctx , evalPayload )
88+ if err != nil {
89+ logger .Error ("Error evaluating CEL expression" , "err" , err )
90+ return false
91+ }
92+ if result .Type () != cel .BoolType {
93+ logger .Error ("CEL evaluation result is not a boolean" , "details" , details )
94+ return false
95+ }
96+ if result .Type () == cel .BoolType && result .Value ().(bool ) {
97+ logger .Error ("Body matched CEL expression" , "expression" , httpConfig .FailIfBodyJsonMatchesCEL .Expression )
98+ return false
99+ }
100+ }
101+
102+ if httpConfig .FailIfBodyJsonNotMatchesCEL != nil {
103+ result , details , err := httpConfig .FailIfBodyJsonNotMatchesCEL .ContextEval (ctx , evalPayload )
104+ if err != nil {
105+ logger .Error ("Error evaluating CEL expression" , "err" , err )
106+ return false
107+ }
108+ if result .Type () != cel .BoolType {
109+ logger .Error ("CEL evaluation result is not a boolean" , "details" , details )
110+ return false
111+ }
112+ if result .Type () == cel .BoolType && ! result .Value ().(bool ) {
113+ logger .Error ("Body did not match CEL expression" , "expression" , httpConfig .FailIfBodyJsonNotMatchesCEL .Expression )
114+ return false
115+ }
116+ }
117+
118+ return true
119+ }
120+
67121func matchRegularExpressionsOnHeaders (header http.Header , httpConfig config.HTTPProbe , logger * slog.Logger ) bool {
68122 for _ , headerMatchSpec := range httpConfig .FailIfHeaderMatchesRegexp {
69123 values := header [textproto .CanonicalMIMEHeaderKey (headerMatchSpec .Header )]
@@ -296,6 +350,11 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
296350 Help : "Indicates if probe failed due to regex" ,
297351 })
298352
353+ probeFailedDueToCEL = prometheus .NewGauge (prometheus.GaugeOpts {
354+ Name : "probe_failed_due_to_cel" ,
355+ Help : "Indicates if probe failed due to CEL expression not matching" ,
356+ })
357+
299358 probeHTTPLastModified = prometheus .NewGauge (prometheus.GaugeOpts {
300359 Name : "probe_http_last_modified_timestamp_seconds" ,
301360 Help : "Returns the Last-Modified HTTP response header in unixtime" ,
@@ -313,6 +372,10 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
313372
314373 httpConfig := module .HTTP
315374
375+ if httpConfig .FailIfBodyJsonMatchesCEL != nil || httpConfig .FailIfBodyJsonNotMatchesCEL != nil {
376+ registry .MustRegister (probeFailedDueToCEL )
377+ }
378+
316379 if ! strings .HasPrefix (target , "http://" ) && ! strings .HasPrefix (target , "https://" ) {
317380 target = "http://" + target
318381 }
@@ -547,6 +610,15 @@ func ProbeHTTP(ctx context.Context, target string, module config.Module, registr
547610 }
548611 }
549612
613+ if success && (httpConfig .FailIfBodyJsonMatchesCEL != nil || httpConfig .FailIfBodyJsonNotMatchesCEL != nil ) {
614+ success = matchCELExpressions (ctx , byteCounter , httpConfig , logger )
615+ if success {
616+ probeFailedDueToCEL .Set (0 )
617+ } else {
618+ probeFailedDueToCEL .Set (1 )
619+ }
620+ }
621+
550622 if ! requestErrored {
551623 _ , err = io .Copy (io .Discard , byteCounter )
552624 if err != nil {
0 commit comments