"errors"
"fmt"
"os"
+ "regexp"
"runtime"
"sync"
"time"
return nil
}
+// Regexp encapsulates a regexp.Regexp and makes it YAML marshalable.
+type Regexp struct {
+ *regexp.Regexp
+ original string
+}
+
+// NewRegexp creates a new anchored Regexp and returns an error if the
+// passed-in regular expression does not compile.
+func NewRegexp(s string) (Regexp, error) {
+ regex, err := regexp.Compile(s)
+ return Regexp{
+ Regexp: regex,
+ original: s,
+ }, err
+}
+
+// UnmarshalYAML implements the yaml.Unmarshaler interface.
+func (re *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error {
+ var s string
+ if err := unmarshal(&s); err != nil {
+ return err
+ }
+ r, err := NewRegexp(s)
+ if err != nil {
+ return fmt.Errorf("\"Could not compile regular expression\" regexp=\"%s\"", s)
+ }
+ *re = r
+ return nil
+}
+
+// MarshalYAML implements the yaml.Marshaler interface.
+func (re Regexp) MarshalYAML() (interface{}, error) {
+ if re.original != "" {
+ return re.original, nil
+ }
+ return nil, nil
+}
+
+// MustNewRegexp works like NewRegexp, but panics if the regular expression does not compile.
+func MustNewRegexp(s string) Regexp {
+ re, err := NewRegexp(s)
+ if err != nil {
+ panic(err)
+ }
+ return re
+}
+
type Module struct {
Prober string `yaml:"prober,omitempty"`
Timeout time.Duration `yaml:"timeout,omitempty"`
FailIfNotSSL bool `yaml:"fail_if_not_ssl,omitempty"`
Method string `yaml:"method,omitempty"`
Headers map[string]string `yaml:"headers,omitempty"`
- FailIfBodyMatchesRegexp []string `yaml:"fail_if_body_matches_regexp,omitempty"`
- FailIfBodyNotMatchesRegexp []string `yaml:"fail_if_body_not_matches_regexp,omitempty"`
+ FailIfBodyMatchesRegexp []Regexp `yaml:"fail_if_body_matches_regexp,omitempty"`
+ FailIfBodyNotMatchesRegexp []Regexp `yaml:"fail_if_body_not_matches_regexp,omitempty"`
FailIfHeaderMatchesRegexp []HeaderMatch `yaml:"fail_if_header_matches,omitempty"`
FailIfHeaderNotMatchesRegexp []HeaderMatch `yaml:"fail_if_header_not_matches,omitempty"`
Body string `yaml:"body,omitempty"`
type HeaderMatch struct {
Header string `yaml:"header,omitempty"`
- Regexp string `yaml:"regexp,omitempty"`
+ Regexp Regexp `yaml:"regexp,omitempty"`
AllowMissing bool `yaml:"allow_missing,omitempty"`
}
type QueryResponse struct {
- Expect string `yaml:"expect,omitempty"`
+ Expect Regexp `yaml:"expect,omitempty"`
Send string `yaml:"send,omitempty"`
StartTLS bool `yaml:"starttls,omitempty"`
}
if err := unmarshal((*plain)(s)); err != nil {
return err
}
+
return nil
}
return errors.New("header name must be set for HTTP header matchers")
}
- if s.Regexp == "" {
+ if s.Regexp.Regexp == nil || s.Regexp.Regexp.String() == "" {
return errors.New("regexp must be set for HTTP header matchers")
}
"net/http/httptrace"
"net/textproto"
"net/url"
- "regexp"
"strconv"
"strings"
"sync"
return false
}
for _, expression := range httpConfig.FailIfBodyMatchesRegexp {
- re, err := regexp.Compile(expression)
- if err != nil {
- level.Error(logger).Log("msg", "Could not compile regular expression", "regexp", expression, "err", err)
- return false
- }
- if re.Match(body) {
+ if expression.Regexp.Match(body) {
level.Error(logger).Log("msg", "Body matched regular expression", "regexp", expression)
return false
}
}
for _, expression := range httpConfig.FailIfBodyNotMatchesRegexp {
- re, err := regexp.Compile(expression)
- if err != nil {
- level.Error(logger).Log("msg", "Could not compile regular expression", "regexp", expression, "err", err)
- return false
- }
- if !re.Match(body) {
+ if !expression.Regexp.Match(body) {
level.Error(logger).Log("msg", "Body did not match regular expression", "regexp", expression)
return false
}
}
}
- re, err := regexp.Compile(headerMatchSpec.Regexp)
- if err != nil {
- level.Error(logger).Log("msg", "Could not compile regular expression", "regexp", headerMatchSpec.Regexp, "err", err)
- return false
- }
-
for _, val := range values {
- if re.MatchString(val) {
+ if headerMatchSpec.Regexp.MatchString(val) {
level.Error(logger).Log("msg", "Header matched regular expression", "header", headerMatchSpec.Header,
"regexp", headerMatchSpec.Regexp, "value_count", len(values))
return false
}
}
- re, err := regexp.Compile(headerMatchSpec.Regexp)
- if err != nil {
- level.Error(logger).Log("msg", "Could not compile regular expression", "regexp", headerMatchSpec.Regexp, "err", err)
- return false
- }
-
anyHeaderValueMatched := false
for _, val := range values {
- if re.MatchString(val) {
+ if headerMatchSpec.Regexp.MatchString(val) {
anyHeaderValueMatched = true
break
}
func TestFailIfBodyMatchesRegexp(t *testing.T) {
testcases := map[string]struct {
respBody string
- regexps []string
+ regexps []config.Regexp
expectedResult bool
}{
"one regex, match": {
respBody: "Bad news: could not connect to database server",
- regexps: []string{"could not connect to database"},
+ regexps: []config.Regexp{config.MustNewRegexp("could not connect to database")},
expectedResult: false,
},
"one regex, no match": {
respBody: "Download the latest version here",
- regexps: []string{"could not connect to database"},
+ regexps: []config.Regexp{config.MustNewRegexp("could not connect to database")},
expectedResult: true,
},
"multiple regexes, match": {
respBody: "internal error",
- regexps: []string{"could not connect to database", "internal error"},
+ regexps: []config.Regexp{config.MustNewRegexp("could not connect to database"), config.MustNewRegexp("internal error")},
expectedResult: false,
},
"multiple regexes, no match": {
respBody: "hello world",
- regexps: []string{"could not connect to database", "internal error"},
+ regexps: []config.Regexp{config.MustNewRegexp("could not connect to database"), config.MustNewRegexp("internal error")},
expectedResult: true,
},
}
testCTX, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result := ProbeHTTP(testCTX, ts.URL,
- config.Module{Timeout: time.Second, HTTP: config.HTTPProbe{IPProtocolFallback: true, FailIfBodyNotMatchesRegexp: []string{"Download the latest version here"}}}, registry, log.NewNopLogger())
+ config.Module{Timeout: time.Second, HTTP: config.HTTPProbe{IPProtocolFallback: true, FailIfBodyNotMatchesRegexp: []config.Regexp{config.MustNewRegexp("Download the latest version here")}}}, registry, log.NewNopLogger())
body := recorder.Body.String()
if result {
t.Fatalf("Regexp test succeeded unexpectedly, got %s", body)
recorder = httptest.NewRecorder()
registry = prometheus.NewRegistry()
result = ProbeHTTP(testCTX, ts.URL,
- config.Module{Timeout: time.Second, HTTP: config.HTTPProbe{IPProtocolFallback: true, FailIfBodyNotMatchesRegexp: []string{"Download the latest version here"}}}, registry, log.NewNopLogger())
+ config.Module{Timeout: time.Second, HTTP: config.HTTPProbe{IPProtocolFallback: true, FailIfBodyNotMatchesRegexp: []config.Regexp{config.MustNewRegexp("Download the latest version here")}}}, registry, log.NewNopLogger())
body = recorder.Body.String()
if !result {
t.Fatalf("Regexp test failed unexpectedly, got %s", body)
recorder = httptest.NewRecorder()
registry = prometheus.NewRegistry()
result = ProbeHTTP(testCTX, ts.URL,
- config.Module{Timeout: time.Second, HTTP: config.HTTPProbe{IPProtocolFallback: true, FailIfBodyNotMatchesRegexp: []string{"Download the latest version here", "Copyright 2015"}}}, registry, log.NewNopLogger())
+ config.Module{Timeout: time.Second, HTTP: config.HTTPProbe{IPProtocolFallback: true, FailIfBodyNotMatchesRegexp: []config.Regexp{config.MustNewRegexp("Download the latest version here"), config.MustNewRegexp("Copyright 2015")}}}, registry, log.NewNopLogger())
body = recorder.Body.String()
if result {
t.Fatalf("Regexp test succeeded unexpectedly, got %s", body)
recorder = httptest.NewRecorder()
registry = prometheus.NewRegistry()
result = ProbeHTTP(testCTX, ts.URL,
- config.Module{Timeout: time.Second, HTTP: config.HTTPProbe{IPProtocolFallback: true, FailIfBodyNotMatchesRegexp: []string{"Download the latest version here", "Copyright 2015"}}}, registry, log.NewNopLogger())
+ config.Module{Timeout: time.Second, HTTP: config.HTTPProbe{IPProtocolFallback: true, FailIfBodyNotMatchesRegexp: []config.Regexp{config.MustNewRegexp("Download the latest version here"), config.MustNewRegexp("Copyright 2015")}}}, registry, log.NewNopLogger())
body = recorder.Body.String()
if !result {
t.Fatalf("Regexp test failed unexpectedly, got %s", body)
Values []string
ShouldSucceed bool
}{
- {config.HeaderMatch{"Content-Type", "text/javascript", false}, []string{"text/javascript"}, false},
- {config.HeaderMatch{"Content-Type", "text/javascript", false}, []string{"application/octet-stream"}, true},
- {config.HeaderMatch{"content-type", "text/javascript", false}, []string{"application/octet-stream"}, true},
- {config.HeaderMatch{"Content-Type", ".*", false}, []string{""}, false},
- {config.HeaderMatch{"Content-Type", ".*", false}, []string{}, false},
- {config.HeaderMatch{"Content-Type", ".*", true}, []string{""}, false},
- {config.HeaderMatch{"Content-Type", ".*", true}, []string{}, true},
- {config.HeaderMatch{"Set-Cookie", ".*Domain=\\.example\\.com.*", false}, []string{"gid=1; Expires=Tue, 19-Mar-2019 20:08:29 GMT; Domain=.example.com; Path=/"}, false},
- {config.HeaderMatch{"Set-Cookie", ".*Domain=\\.example\\.com.*", false}, []string{"zz=4; expires=Mon, 01-Jan-1990 00:00:00 GMT; Domain=www.example.com; Path=/", "gid=1; Expires=Tue, 19-Mar-2019 20:08:29 GMT; Domain=.example.com; Path=/"}, false},
+ {config.HeaderMatch{"Content-Type", config.MustNewRegexp("text/javascript"), false}, []string{"text/javascript"}, false},
+ {config.HeaderMatch{"Content-Type", config.MustNewRegexp("text/javascript"), false}, []string{"application/octet-stream"}, true},
+ {config.HeaderMatch{"content-type", config.MustNewRegexp("text/javascript"), false}, []string{"application/octet-stream"}, true},
+ {config.HeaderMatch{"Content-Type", config.MustNewRegexp(".*"), false}, []string{""}, false},
+ {config.HeaderMatch{"Content-Type", config.MustNewRegexp(".*"), false}, []string{}, false},
+ {config.HeaderMatch{"Content-Type", config.MustNewRegexp(".*"), true}, []string{""}, false},
+ {config.HeaderMatch{"Content-Type", config.MustNewRegexp(".*"), true}, []string{}, true},
+ {config.HeaderMatch{"Set-Cookie", config.MustNewRegexp(".*Domain=\\.example\\.com.*"), false}, []string{"gid=1; Expires=Tue, 19-Mar-2019 20:08:29 GMT; Domain=.example.com; Path=/"}, false},
+ {config.HeaderMatch{"Set-Cookie", config.MustNewRegexp(".*Domain=\\.example\\.com.*"), false}, []string{"zz=4; expires=Mon, 01-Jan-1990 00:00:00 GMT; Domain=www.example.com; Path=/", "gid=1; Expires=Tue, 19-Mar-2019 20:08:29 GMT; Domain=.example.com; Path=/"}, false},
}
for i, test := range tests {
Values []string
ShouldSucceed bool
}{
- {config.HeaderMatch{"Content-Type", "text/javascript", false}, []string{"text/javascript"}, true},
- {config.HeaderMatch{"content-type", "text/javascript", false}, []string{"text/javascript"}, true},
- {config.HeaderMatch{"Content-Type", "text/javascript", false}, []string{"application/octet-stream"}, false},
- {config.HeaderMatch{"Content-Type", ".*", false}, []string{""}, true},
- {config.HeaderMatch{"Content-Type", ".*", false}, []string{}, false},
- {config.HeaderMatch{"Content-Type", ".*", true}, []string{}, true},
- {config.HeaderMatch{"Set-Cookie", ".*Domain=\\.example\\.com.*", false}, []string{"zz=4; expires=Mon, 01-Jan-1990 00:00:00 GMT; Domain=www.example.com; Path=/"}, false},
- {config.HeaderMatch{"Set-Cookie", ".*Domain=\\.example\\.com.*", false}, []string{"zz=4; expires=Mon, 01-Jan-1990 00:00:00 GMT; Domain=www.example.com; Path=/", "gid=1; Expires=Tue, 19-Mar-2019 20:08:29 GMT; Domain=.example.com; Path=/"}, true},
+ {config.HeaderMatch{"Content-Type", config.MustNewRegexp("text/javascript"), false}, []string{"text/javascript"}, true},
+ {config.HeaderMatch{"content-type", config.MustNewRegexp("text/javascript"), false}, []string{"text/javascript"}, true},
+ {config.HeaderMatch{"Content-Type", config.MustNewRegexp("text/javascript"), false}, []string{"application/octet-stream"}, false},
+ {config.HeaderMatch{"Content-Type", config.MustNewRegexp(".*"), false}, []string{""}, true},
+ {config.HeaderMatch{"Content-Type", config.MustNewRegexp(".*"), false}, []string{}, false},
+ {config.HeaderMatch{"Content-Type", config.MustNewRegexp(".*"), true}, []string{}, true},
+ {config.HeaderMatch{"Set-Cookie", config.MustNewRegexp(".*Domain=\\.example\\.com.*"), false}, []string{"zz=4; expires=Mon, 01-Jan-1990 00:00:00 GMT; Domain=www.example.com; Path=/"}, false},
+ {config.HeaderMatch{"Set-Cookie", config.MustNewRegexp(".*Domain=\\.example\\.com.*"), false}, []string{"zz=4; expires=Mon, 01-Jan-1990 00:00:00 GMT; Domain=www.example.com; Path=/", "gid=1; Expires=Tue, 19-Mar-2019 20:08:29 GMT; Domain=.example.com; Path=/"}, true},
}
for i, test := range tests {