diff --git a/validators/proto/config.proto b/validators/proto/config.proto index d129cceb..cda5234a 100644 --- a/validators/proto/config.proto +++ b/validators/proto/config.proto @@ -2,6 +2,7 @@ syntax = "proto2"; import "github.com/google/cloudprober/validators/http/proto/config.proto"; import "github.com/google/cloudprober/validators/integrity/proto/config.proto"; +import "github.com/google/cloudprober/validators/regex/proto/config.proto"; package cloudprober.validators; @@ -12,5 +13,8 @@ message Validator { // Data integrity validator integrity.Validator integrity_validator = 3; + + // Regex validator + regex.Validator regex_validator = 4; } } diff --git a/validators/regex/proto/config.proto b/validators/regex/proto/config.proto new file mode 100644 index 00000000..a4153ede --- /dev/null +++ b/validators/regex/proto/config.proto @@ -0,0 +1,7 @@ +syntax = "proto2"; + +package cloudprober.validators.regex; + +message Validator { + optional string regex = 1; +} diff --git a/validators/regex/regex.go b/validators/regex/regex.go new file mode 100644 index 00000000..7e53d91a --- /dev/null +++ b/validators/regex/regex.go @@ -0,0 +1,61 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package regex provides regex validator for the Cloudprober's +// validator framework. +package regex + +import ( + "errors" + "fmt" + "regexp" + + "github.com/google/cloudprober/logger" + configpb "github.com/google/cloudprober/validators/regex/proto" +) + +// Validator implements a regex validator. +type Validator struct { + r *regexp.Regexp + l *logger.Logger +} + +// Init initializes the regex validator. +// It compiles the regex in the configuration and returns an error if regex +// doesn't compile for some reason. +func (v *Validator) Init(config interface{}, l *logger.Logger) error { + c, ok := config.(*configpb.Validator) + if !ok { + return fmt.Errorf("%v is not a valid regex validator config", config) + } + + if c.GetRegex() == "" { + return errors.New("validator regex string cannot be empty") + } + + r, err := regexp.Compile(c.GetRegex()) + if err != nil { + return fmt.Errorf("error compiling the given regex (%s): %v", c.GetRegex(), err) + } + + v.r = r + v.l = l + return nil +} + +// Validate the provided responseBody and return true if responseBody matches +// the configured regex. +func (v *Validator) Validate(unusedResponseObj interface{}, responseBody []byte) (bool, error) { + return v.r.Match(responseBody), nil +} diff --git a/validators/regex/regex_test.go b/validators/regex/regex_test.go new file mode 100644 index 00000000..c5171127 --- /dev/null +++ b/validators/regex/regex_test.go @@ -0,0 +1,92 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package regex + +import ( + "testing" + + "github.com/golang/protobuf/proto" + "github.com/google/cloudprober/logger" + configpb "github.com/google/cloudprober/validators/regex/proto" +) + +func TestInvalidConfig(t *testing.T) { + // Empty config + testConfig := &configpb.Validator{} + v := Validator{} + err := v.Init(testConfig, &logger.Logger{}) + if err == nil { + t.Errorf("v.Init(%v, l): expected error but got nil", testConfig) + } + + // Invalid regex as Go regex doesn't support negative lookaheads. + testConfig = &configpb.Validator{ + Regex: proto.String("(?!cloudprober)"), + } + v = Validator{} + err = v.Init(testConfig, &logger.Logger{}) + if err == nil { + t.Errorf("v.Init(%v, l): expected error but got nil", testConfig) + } +} + +func verifyValidate(t *testing.T, respBody []byte, regex string, expected bool) { + t.Helper() + // Test initializing with pattern string. + testConfig := &configpb.Validator{ + Regex: proto.String(regex), + } + + v := Validator{} + err := v.Init(testConfig, &logger.Logger{}) + if err != nil { + t.Errorf("v.Init(%v, l): got error: %v", testConfig, err) + } + + result, err := v.Validate(nil, respBody) + if err != nil { + t.Errorf("v.Validate(nil, %s): got error: %v", string(respBody), err) + } + + if result != expected { + if err != nil { + t.Errorf("v.Validate(nil, %s): result: %v, expected: %v", string(respBody), result, expected) + } + } +} + +func TestPatternString(t *testing.T) { + rows := []struct { + regex string + respBody []byte + expected bool + }{ + { + regex: "cloud.*", + respBody: []byte("cloudprober"), + expected: true, + }, + { + regex: "[Cc]loud.*", + respBody: []byte("Cloudprober"), + expected: false, + }, + } + + for _, r := range rows { + verifyValidate(t, r.respBody, r.regex, r.expected) + } + +} diff --git a/validators/validators.go b/validators/validators.go index 3ef74c2b..9430b8d1 100644 --- a/validators/validators.go +++ b/validators/validators.go @@ -23,6 +23,7 @@ import ( "github.com/google/cloudprober/validators/http" "github.com/google/cloudprober/validators/integrity" configpb "github.com/google/cloudprober/validators/proto" + "github.com/google/cloudprober/validators/regex" ) // Validator interface represents a validator. @@ -60,6 +61,9 @@ func initValidator(validatorConf *configpb.Validator, l *logger.Logger) (validat case *configpb.Validator_IntegrityValidator: validator = &integrity.Validator{} c = validatorConf.GetIntegrityValidator() + case *configpb.Validator_RegexValidator: + validator = ®ex.Validator{} + c = validatorConf.GetRegexValidator() default: err = fmt.Errorf("unknown validator type: %v", validatorConf.Type) return