diff --git a/api/v1/api.go b/api/v1/api.go index 0a1df61b..1aa60d99 100644 --- a/api/v1/api.go +++ b/api/v1/api.go @@ -684,10 +684,16 @@ func silenceToProto(s *types.Silence) (*silencepb.Silence, error) { matcher := &silencepb.Matcher{ Name: m.Name, Pattern: m.Value, - Type: silencepb.Matcher_EQUAL, } - if m.IsRegex { + switch m.Type { + case labels.MatchEqual: + matcher.Type = silencepb.Matcher_EQUAL + case labels.MatchNotEqual: + matcher.Type = silencepb.Matcher_NOT_EQUAL + case labels.MatchRegexp: matcher.Type = silencepb.Matcher_REGEXP + case labels.MatchNotRegexp: + matcher.Type = silencepb.Matcher_NOT_REGEXP } sil.Matchers = append(sil.Matchers, matcher) } @@ -707,17 +713,22 @@ func silenceFromProto(s *silencepb.Silence) (*types.Silence, error) { CreatedBy: s.CreatedBy, } for _, m := range s.Matchers { - matcher := &types.Matcher{ - Name: m.Name, - Value: m.Pattern, - } + var t labels.MatchType switch m.Type { case silencepb.Matcher_EQUAL: + t = labels.MatchEqual + case silencepb.Matcher_NOT_EQUAL: + t = labels.MatchNotEqual case silencepb.Matcher_REGEXP: - matcher.IsRegex = true - default: - return nil, fmt.Errorf("unknown matcher type") + t = labels.MatchRegexp + case silencepb.Matcher_NOT_REGEXP: + t = labels.MatchNotRegexp } + matcher, err := labels.NewMatcher(t, m.Name, m.Pattern) + if err != nil { + return nil, err + } + sil.Matchers = append(sil.Matchers, matcher) } diff --git a/api/v1/api_test.go b/api/v1/api_test.go index d25f3628..d8c4483d 100644 --- a/api/v1/api_test.go +++ b/api/v1/api_test.go @@ -572,10 +572,14 @@ func TestMatchFilterLabels(t *testing.T) { } } -func newMatcher(labelSet model.LabelSet) types.Matchers { - matchers := make([]*types.Matcher, 0, len(labelSet)) +func newMatcher(labelSet model.LabelSet) labels.Matchers { + matchers := make([]*labels.Matcher, 0, len(labelSet)) for key, val := range labelSet { - matchers = append(matchers, types.NewMatcher(key, string(val))) + matchers = append(matchers, &labels.Matcher{ + Type: labels.MatchEqual, + Name: string(key), + Value: string(val), + }) } return matchers } diff --git a/client/client_test.go b/client/client_test.go index eb43c69b..86d845b2 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -25,6 +25,7 @@ import ( "time" "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/types" ) @@ -136,11 +137,11 @@ func TestAPI(t *testing.T) { silOne := &types.Silence{ ID: "abc", - Matchers: []*types.Matcher{ + Matchers: []*labels.Matcher{ { - Name: "label1", - Value: "test1", - IsRegex: false, + Name: "label1", + Value: "test1", + Type: labels.MatchEqual, }, }, StartsAt: now, diff --git a/pkg/labels/matcher.go b/pkg/labels/matcher.go index a0f97413..8fea7877 100644 --- a/pkg/labels/matcher.go +++ b/pkg/labels/matcher.go @@ -152,3 +152,11 @@ func (ms Matchers) String() string { return buf.String() } + +// NewRegexpMatcher returns a matcher with already compiled regexp.Regexp. +// +// TODO(vladimiroff): Get rid of this function once migration from +// types.Matcher is complete. +func NewRegexpMatcher(n string, re *regexp.Regexp) *Matcher { + return &Matcher{Type: MatchRegexp, Name: n, Value: re.String(), re: re} +} diff --git a/silence/silence.go b/silence/silence.go index c21c5c58..f0a22e7b 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -32,6 +32,7 @@ import ( "github.com/matttproud/golang_protobuf_extensions/pbutil" "github.com/pkg/errors" "github.com/prometheus/alertmanager/cluster" + "github.com/prometheus/alertmanager/pkg/labels" pb "github.com/prometheus/alertmanager/silence/silencepb" "github.com/prometheus/alertmanager/types" "github.com/prometheus/client_golang/prometheus" @@ -49,12 +50,12 @@ func utcNow() time.Time { return time.Now().UTC() } -type matcherCache map[*pb.Silence]types.Matchers +type matcherCache map[*pb.Silence]labels.Matchers // Get retrieves the matchers for a given silence. If it is a missed cache // access, it compiles and adds the matchers of the requested silence to the // cache. -func (c matcherCache) Get(s *pb.Silence) (types.Matchers, error) { +func (c matcherCache) Get(s *pb.Silence) (labels.Matchers, error) { if m, ok := c[s]; ok { return m, nil } @@ -63,33 +64,30 @@ func (c matcherCache) Get(s *pb.Silence) (types.Matchers, error) { // add compiles a silences' matchers and adds them to the cache. // It returns the compiled matchers. -func (c matcherCache) add(s *pb.Silence) (types.Matchers, error) { - var ( - ms types.Matchers - mt *types.Matcher - ) +func (c matcherCache) add(s *pb.Silence) (labels.Matchers, error) { + ms := make(labels.Matchers, len(s.Matchers)) - for _, m := range s.Matchers { - mt = &types.Matcher{ - Name: m.Name, - Value: m.Pattern, - } + for i, m := range s.Matchers { + var mt labels.MatchType switch m.Type { case pb.Matcher_EQUAL: - mt.IsRegex = false + mt = labels.MatchEqual + case pb.Matcher_NOT_EQUAL: + mt = labels.MatchNotEqual case pb.Matcher_REGEXP: - mt.IsRegex = true + mt = labels.MatchRegexp + case pb.Matcher_NOT_REGEXP: + mt = labels.MatchNotRegexp } - err := mt.Init() + matcher, err := labels.NewMatcher(mt, m.Name, m.Pattern) if err != nil { return nil, err } - ms = append(ms, mt) + ms[i] = matcher } c[s] = ms - return ms, nil } @@ -634,7 +632,7 @@ func QMatches(set model.LabelSet) QueryParam { if err != nil { return true, err } - return m.Match(set), nil + return m.Matches(set), nil } q.filters = append(q.filters, f) return nil diff --git a/silence/silencepb/silence.pb.go b/silence/silencepb/silence.pb.go index 10502c72..ec878fc2 100644 --- a/silence/silencepb/silence.pb.go +++ b/silence/silencepb/silence.pb.go @@ -33,18 +33,24 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type Matcher_Type int32 const ( - Matcher_EQUAL Matcher_Type = 0 - Matcher_REGEXP Matcher_Type = 1 + Matcher_EQUAL Matcher_Type = 0 + Matcher_REGEXP Matcher_Type = 1 + Matcher_NOT_EQUAL Matcher_Type = 2 + Matcher_NOT_REGEXP Matcher_Type = 3 ) var Matcher_Type_name = map[int32]string{ 0: "EQUAL", 1: "REGEXP", + 2: "NOT_EQUAL", + 3: "NOT_REGEXP", } var Matcher_Type_value = map[string]int32{ - "EQUAL": 0, - "REGEXP": 1, + "EQUAL": 0, + "REGEXP": 1, + "NOT_EQUAL": 2, + "NOT_REGEXP": 3, } func (x Matcher_Type) String() string { @@ -254,35 +260,36 @@ func init() { func init() { proto.RegisterFile("silence.proto", fileDescriptor_7fc56058cf68dbd8) } var fileDescriptor_7fc56058cf68dbd8 = []byte{ - // 444 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x51, 0x4d, 0x6b, 0xdb, 0x40, - 0x10, 0xf5, 0x2a, 0x8e, 0x65, 0x8d, 0x69, 0x30, 0x43, 0x69, 0x85, 0x21, 0xb6, 0xd1, 0xc9, 0xd0, - 0x22, 0x83, 0x7b, 0xee, 0x41, 0x0e, 0xa6, 0x97, 0x06, 0x5a, 0x35, 0x85, 0xde, 0xca, 0xda, 0x9a, - 0xda, 0x82, 0x48, 0xbb, 0x48, 0x63, 0xa8, 0x4f, 0x2d, 0xf4, 0x0f, 0xf4, 0x67, 0xf9, 0xd8, 0x5f, - 0xd0, 0x0f, 0xff, 0x8b, 0xde, 0x8a, 0x56, 0x2b, 0x37, 0x21, 0x27, 0xdf, 0x66, 0x66, 0xdf, 0x9b, - 0xb7, 0xef, 0x0d, 0x3c, 0x2a, 0xd3, 0x5b, 0xca, 0x57, 0x14, 0xea, 0x42, 0xb1, 0x42, 0xcf, 0xb6, - 0x7a, 0x39, 0x18, 0xad, 0x95, 0x5a, 0xdf, 0xd2, 0xd4, 0x3c, 0x2c, 0xb7, 0x9f, 0xa6, 0x9c, 0x66, - 0x54, 0xb2, 0xcc, 0x74, 0x8d, 0x1d, 0x3c, 0x5e, 0xab, 0xb5, 0x32, 0xe5, 0xb4, 0xaa, 0xea, 0x69, - 0xf0, 0x4d, 0x80, 0x7b, 0x2d, 0x79, 0xb5, 0xa1, 0x02, 0x9f, 0x41, 0x9b, 0x77, 0x9a, 0x7c, 0x31, - 0x16, 0x93, 0x8b, 0xd9, 0xd3, 0xf0, 0xb8, 0x3c, 0xb4, 0x88, 0xf0, 0x66, 0xa7, 0x29, 0x36, 0x20, - 0x44, 0x68, 0xe7, 0x32, 0x23, 0xdf, 0x19, 0x8b, 0x89, 0x17, 0x9b, 0x1a, 0x7d, 0x70, 0xb5, 0x64, - 0xa6, 0x22, 0xf7, 0xcf, 0xcc, 0xb8, 0x69, 0x83, 0x4b, 0x68, 0x57, 0x5c, 0xf4, 0xe0, 0x7c, 0xf1, - 0xf6, 0x7d, 0xf4, 0xba, 0xdf, 0x42, 0x80, 0x4e, 0xbc, 0x78, 0xb5, 0xf8, 0xf0, 0xa6, 0x2f, 0x82, - 0x2f, 0xe0, 0x5e, 0xa9, 0x2c, 0xa3, 0x9c, 0xf1, 0x09, 0x74, 0xe4, 0x96, 0x37, 0xaa, 0x30, 0xdf, - 0xf0, 0x62, 0xdb, 0x55, 0xbb, 0x57, 0x35, 0xc4, 0x4a, 0x36, 0x2d, 0xce, 0xc1, 0x3b, 0x7a, 0x35, - 0xba, 0xbd, 0xd9, 0x20, 0xac, 0xd3, 0x08, 0x9b, 0x34, 0xc2, 0x9b, 0x06, 0x31, 0xef, 0xee, 0x7f, - 0x8e, 0x5a, 0xdf, 0x7f, 0x8d, 0x44, 0xfc, 0x9f, 0x16, 0xfc, 0x75, 0xc0, 0x7d, 0x57, 0xdb, 0xc5, - 0x0b, 0x70, 0xd2, 0xc4, 0xaa, 0x3b, 0x69, 0x82, 0x21, 0x74, 0xb3, 0xda, 0x7f, 0xe9, 0x3b, 0xe3, - 0xb3, 0x49, 0x6f, 0x86, 0x0f, 0xa3, 0x89, 0x8f, 0x18, 0x8c, 0xc0, 0x2b, 0x59, 0x16, 0x5c, 0x7e, - 0x94, 0x7c, 0xd2, 0x7f, 0xba, 0x35, 0x2d, 0x62, 0x7c, 0x09, 0x2e, 0xe5, 0x89, 0x59, 0xd0, 0x3e, - 0x61, 0x41, 0xa7, 0x22, 0x45, 0x8c, 0x57, 0x00, 0x5b, 0x9d, 0x48, 0xa6, 0xa4, 0xda, 0x70, 0x7e, - 0x4a, 0x24, 0x96, 0x17, 0x71, 0x65, 0xdb, 0x26, 0x5c, 0xfa, 0xee, 0x03, 0xdb, 0xf6, 0x5c, 0xf1, - 0x11, 0x83, 0x97, 0x00, 0xab, 0x82, 0x8c, 0xe8, 0x72, 0xe7, 0x77, 0x4d, 0x7c, 0x9e, 0x9d, 0xcc, - 0x77, 0x77, 0xef, 0xe7, 0xdd, 0xbb, 0x5f, 0xf0, 0x55, 0x40, 0xef, 0x9a, 0xca, 0x4d, 0x93, 0xff, - 0x73, 0x70, 0xad, 0x8e, 0x39, 0xc2, 0x7d, 0x5d, 0x0b, 0x8a, 0x1b, 0x48, 0xe5, 0x95, 0x3e, 0xeb, - 0xb4, 0x20, 0x93, 0x96, 0x73, 0x8a, 0x57, 0xcb, 0x8b, 0x78, 0xde, 0xdf, 0xff, 0x19, 0xb6, 0xf6, - 0x87, 0xa1, 0xf8, 0x71, 0x18, 0x8a, 0xdf, 0x87, 0xa1, 0x58, 0x76, 0x0c, 0xf5, 0xc5, 0xbf, 0x00, - 0x00, 0x00, 0xff, 0xff, 0xde, 0x36, 0xea, 0xdd, 0x71, 0x03, 0x00, 0x00, + // 461 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0xcd, 0x8e, 0xd2, 0x50, + 0x14, 0xc7, 0xb9, 0x85, 0xa1, 0xdc, 0x43, 0x86, 0x90, 0x13, 0xa3, 0x0d, 0x89, 0x40, 0xba, 0x22, + 0xd1, 0x94, 0x04, 0xb7, 0xba, 0x28, 0x13, 0xe2, 0xc6, 0xf1, 0xa3, 0x62, 0xe2, 0x6e, 0x52, 0xe8, + 0x11, 0x9a, 0x4c, 0x3f, 0xd2, 0x1e, 0x12, 0x59, 0xe9, 0x23, 0xf8, 0x0c, 0x3e, 0x0d, 0x4b, 0x9f, + 0xc0, 0x0f, 0xde, 0xc2, 0x9d, 0xe9, 0xed, 0x2d, 0xce, 0x84, 0x55, 0x77, 0xf7, 0xdc, 0xf3, 0xff, + 0x9f, 0x8f, 0xdf, 0x81, 0xcb, 0x3c, 0xbc, 0xa5, 0x78, 0x4d, 0x4e, 0x9a, 0x25, 0x9c, 0xa0, 0xd4, + 0x61, 0xba, 0x1a, 0x8c, 0x36, 0x49, 0xb2, 0xb9, 0xa5, 0xa9, 0x4a, 0xac, 0x76, 0x9f, 0xa6, 0x1c, + 0x46, 0x94, 0xb3, 0x1f, 0xa5, 0xa5, 0x76, 0xf0, 0x60, 0x93, 0x6c, 0x12, 0xf5, 0x9c, 0x16, 0xaf, + 0xf2, 0xd7, 0xfe, 0x2e, 0xc0, 0xbc, 0xf6, 0x79, 0xbd, 0xa5, 0x0c, 0x9f, 0x40, 0x8b, 0xf7, 0x29, + 0x59, 0x62, 0x2c, 0x26, 0xbd, 0xd9, 0x23, 0xe7, 0x54, 0xdc, 0xd1, 0x0a, 0x67, 0xb9, 0x4f, 0xc9, + 0x53, 0x22, 0x44, 0x68, 0xc5, 0x7e, 0x44, 0x96, 0x31, 0x16, 0x13, 0xe9, 0xa9, 0x37, 0x5a, 0x60, + 0xa6, 0x3e, 0x33, 0x65, 0xb1, 0xd5, 0x54, 0xdf, 0x55, 0x68, 0x3f, 0x87, 0x56, 0xe1, 0x45, 0x09, + 0x17, 0x8b, 0x77, 0x1f, 0xdc, 0x57, 0xfd, 0x06, 0x02, 0xb4, 0xbd, 0xc5, 0xcb, 0xc5, 0xc7, 0xb7, + 0x7d, 0x81, 0x97, 0x20, 0x5f, 0xbf, 0x59, 0xde, 0x94, 0x29, 0x03, 0x7b, 0x00, 0x45, 0xa8, 0xd3, + 0x4d, 0xfb, 0x0b, 0x98, 0x57, 0x49, 0x14, 0x51, 0xcc, 0xf8, 0x10, 0xda, 0xfe, 0x8e, 0xb7, 0x49, + 0xa6, 0xa6, 0x94, 0x9e, 0x8e, 0x8a, 0xd6, 0xeb, 0x52, 0xa2, 0x27, 0xaa, 0x42, 0x9c, 0x83, 0x3c, + 0xa1, 0x50, 0x63, 0x75, 0x67, 0x03, 0xa7, 0x84, 0xe5, 0x54, 0xb0, 0x9c, 0x65, 0xa5, 0x98, 0x77, + 0x0e, 0x3f, 0x47, 0x8d, 0x6f, 0xbf, 0x46, 0xc2, 0xfb, 0x6f, 0xb3, 0xff, 0x1a, 0x60, 0xbe, 0x2f, + 0x69, 0x60, 0x0f, 0x8c, 0x30, 0xd0, 0xdd, 0x8d, 0x30, 0x40, 0x07, 0x3a, 0x51, 0x89, 0x27, 0xb7, + 0x8c, 0x71, 0x73, 0xd2, 0x9d, 0xe1, 0x39, 0x39, 0xef, 0xa4, 0x41, 0x17, 0x64, 0xce, 0x7e, 0xc6, + 0xf9, 0x8d, 0xcf, 0xb5, 0xe6, 0xe9, 0x94, 0x36, 0x97, 0xf1, 0x05, 0x98, 0x14, 0x07, 0xaa, 0x40, + 0xab, 0x46, 0x81, 0x76, 0x61, 0x72, 0x19, 0xaf, 0x00, 0x76, 0x69, 0xe0, 0x33, 0x05, 0x45, 0x85, + 0x8b, 0x3a, 0x48, 0xb4, 0xcf, 0xe5, 0x62, 0x6d, 0x4d, 0x38, 0xb7, 0xcc, 0xb3, 0xb5, 0xf5, 0xb9, + 0xbc, 0x93, 0x06, 0x1f, 0x03, 0xac, 0x33, 0x52, 0x4d, 0x57, 0x7b, 0xab, 0xa3, 0xf0, 0x49, 0xfd, + 0x33, 0xdf, 0xdf, 0xbd, 0x9f, 0xbc, 0x77, 0x3f, 0xfb, 0xab, 0x80, 0xee, 0x35, 0xe5, 0xdb, 0x8a, + 0xff, 0x53, 0x30, 0x75, 0x1f, 0x75, 0x84, 0xfb, 0x7d, 0xb5, 0xc8, 0xab, 0x24, 0xc5, 0xae, 0xf4, + 0x39, 0x0d, 0x33, 0x52, 0xb4, 0x8c, 0x3a, 0xbb, 0x6a, 0x9f, 0xcb, 0xf3, 0xfe, 0xe1, 0xcf, 0xb0, + 0x71, 0x38, 0x0e, 0xc5, 0x8f, 0xe3, 0x50, 0xfc, 0x3e, 0x0e, 0xc5, 0xaa, 0xad, 0xac, 0xcf, 0xfe, + 0x05, 0x00, 0x00, 0xff, 0xff, 0xad, 0x09, 0x3a, 0xe9, 0x90, 0x03, 0x00, 0x00, } func (m *Matcher) Marshal() (dAtA []byte, err error) { diff --git a/silence/silencepb/silence.proto b/silence/silencepb/silence.proto index 9a62d9c5..77868c71 100644 --- a/silence/silencepb/silence.proto +++ b/silence/silencepb/silence.proto @@ -17,6 +17,8 @@ message Matcher { enum Type { EQUAL = 0; REGEXP = 1; + NOT_EQUAL = 2; + NOT_REGEXP = 3; }; Type type = 1; @@ -63,4 +65,4 @@ message Silence { message MeshSilence { Silence silence = 1; google.protobuf.Timestamp expires_at = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; -} \ No newline at end of file +} diff --git a/test/with_api_v1/mock.go b/test/with_api_v1/mock.go index 17151713..d299db9e 100644 --- a/test/with_api_v1/mock.go +++ b/test/with_api_v1/mock.go @@ -25,6 +25,7 @@ import ( "github.com/prometheus/common/model" "github.com/prometheus/alertmanager/notify/webhook" + "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/types" ) @@ -106,17 +107,18 @@ func (s *TestSilence) nativeSilence(opts *AcceptanceOpts) *types.Silence { nsil := &types.Silence{} for i := 0; i < len(s.match); i += 2 { - nsil.Matchers = append(nsil.Matchers, &types.Matcher{ + nsil.Matchers = append(nsil.Matchers, &labels.Matcher{ + Type: labels.MatchEqual, Name: s.match[i], Value: s.match[i+1], }) } for i := 0; i < len(s.matchRE); i += 2 { - nsil.Matchers = append(nsil.Matchers, &types.Matcher{ - Name: s.matchRE[i], - Value: s.matchRE[i+1], - IsRegex: true, - }) + m, err := labels.NewMatcher(labels.MatchRegexp, s.matchRE[i], s.matchRE[i+1]) + if err != nil { + panic(err) + } + nsil.Matchers = append(nsil.Matchers, m) } if s.startsAt > 0 { diff --git a/types/match.go b/types/match.go deleted file mode 100644 index d84f480d..00000000 --- a/types/match.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2015 Prometheus Team -// 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 types - -import ( - "bytes" - "fmt" - "regexp" - "sort" - - "github.com/prometheus/common/model" -) - -// Matcher defines a matching rule for the value of a given label. -// -// TODO(asquare14): This is now only used for silences and inhibitions and will be replaced by labels.Matchers. -type Matcher struct { - Name string `json:"name"` - Value string `json:"value"` - IsRegex bool `json:"isRegex"` - - regex *regexp.Regexp -} - -// Init internals of the Matcher. Must be called before using Match. -func (m *Matcher) Init() error { - if !m.IsRegex { - return nil - } - re, err := regexp.Compile("^(?:" + m.Value + ")$") - if err != nil { - return err - } - m.regex = re - return nil -} - -func (m *Matcher) String() string { - if m.IsRegex { - return fmt.Sprintf("%s=~%q", m.Name, m.Value) - } - return fmt.Sprintf("%s=%q", m.Name, m.Value) -} - -// Validate returns true iff all fields of the matcher have valid values. -func (m *Matcher) Validate() error { - if !model.LabelName(m.Name).IsValid() { - return fmt.Errorf("invalid name %q", m.Name) - } - if m.IsRegex { - if _, err := regexp.Compile(m.Value); err != nil { - return fmt.Errorf("invalid regular expression %q", m.Value) - } - } else if !model.LabelValue(m.Value).IsValid() || len(m.Value) == 0 { - return fmt.Errorf("invalid value %q", m.Value) - } - return nil -} - -// Match checks whether the label of the matcher has the specified -// matching value. -func (m *Matcher) Match(lset model.LabelSet) bool { - // Unset labels are treated as unset labels globally. Thus, if a - // label is not set we retrieve the empty label which is correct - // for the comparison below. - v := lset[model.LabelName(m.Name)] - - if m.IsRegex { - return m.regex.MatchString(string(v)) - } - return string(v) == m.Value -} - -// NewMatcher returns a new matcher that compares against equality of -// the given value. -func NewMatcher(name model.LabelName, value string) *Matcher { - return &Matcher{ - Name: string(name), - Value: value, - IsRegex: false, - } -} - -// NewRegexMatcher returns a new matcher that compares values against -// a regular expression. The matcher is already initialized. -// -// TODO(fabxc): refactor usage. -func NewRegexMatcher(name model.LabelName, re *regexp.Regexp) *Matcher { - return &Matcher{ - Name: string(name), - Value: re.String(), - IsRegex: true, - regex: re, - } -} - -// Matchers provides the Match and Fingerprint methods for a slice of Matchers. -// Matchers must always be sorted. -type Matchers []*Matcher - -// NewMatchers returns the given Matchers sorted. -func NewMatchers(ms ...*Matcher) Matchers { - m := Matchers(ms) - sort.Sort(m) - return m -} - -func (ms Matchers) Len() int { return len(ms) } -func (ms Matchers) Swap(i, j int) { ms[i], ms[j] = ms[j], ms[i] } - -func (ms Matchers) Less(i, j int) bool { - if ms[i].Name > ms[j].Name { - return false - } - if ms[i].Name < ms[j].Name { - return true - } - if ms[i].Value > ms[j].Value { - return false - } - if ms[i].Value < ms[j].Value { - return true - } - return !ms[i].IsRegex && ms[j].IsRegex -} - -// Match checks whether all matchers are fulfilled against the given label set. -func (ms Matchers) Match(lset model.LabelSet) bool { - for _, m := range ms { - if !m.Match(lset) { - return false - } - } - return true -} - -func (ms Matchers) String() string { - var buf bytes.Buffer - - buf.WriteByte('{') - for i, m := range ms { - if i > 0 { - buf.WriteByte(',') - } - buf.WriteString(m.String()) - } - buf.WriteByte('}') - - return buf.String() -} diff --git a/types/match_test.go b/types/match_test.go deleted file mode 100644 index 94b90b37..00000000 --- a/types/match_test.go +++ /dev/null @@ -1,270 +0,0 @@ -// Copyright 2018 Prometheus Team -// 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 types - -import ( - "fmt" - "regexp" - "testing" - - "github.com/prometheus/common/model" - "github.com/stretchr/testify/require" -) - -func TestMatcherValidate(t *testing.T) { - - validLabelName := "valid_label_name" - validStringValue := "value" - validRegexValue := ".*" - - invalidLabelName := "123_invalid_name" - invalidStringValue := "" - invalidRegexValue := "]*.[" - - tests := []struct { - matcher Matcher - valid bool - errorMsg string - }{ - //valid tests - { - matcher: Matcher{Name: validLabelName, Value: validStringValue}, - valid: true, - }, - { - matcher: Matcher{Name: validLabelName, Value: validRegexValue, IsRegex: true}, - valid: true, - }, - // invalid tests - { - matcher: Matcher{Name: invalidLabelName, Value: validStringValue}, - valid: false, - errorMsg: fmt.Sprintf("invalid name %q", invalidLabelName), - }, - { - matcher: Matcher{Name: validLabelName, Value: invalidStringValue}, - valid: false, - errorMsg: fmt.Sprintf("invalid value %q", invalidStringValue), - }, - { - matcher: Matcher{Name: validLabelName, Value: invalidRegexValue, IsRegex: true}, - valid: false, - errorMsg: fmt.Sprintf("invalid regular expression %q", invalidRegexValue), - }, - } - - for _, test := range tests { - test.matcher.Init() - - if test.valid { - require.NoError(t, test.matcher.Validate()) - continue - } - - require.EqualError(t, test.matcher.Validate(), test.errorMsg) - } -} - -func TestMatcherInit(t *testing.T) { - m := Matcher{Name: "label", Value: ".*", IsRegex: true} - require.NoError(t, m.Init()) - require.EqualValues(t, "^(?:.*)$", m.regex.String()) - - m = Matcher{Name: "label", Value: "]*.[", IsRegex: true} - require.Error(t, m.Init()) -} - -func TestMatcherMatch(t *testing.T) { - tests := []struct { - matcher Matcher - expected bool - }{ - {matcher: Matcher{Name: "label", Value: "value"}, expected: true}, - {matcher: Matcher{Name: "label", Value: "val"}, expected: false}, - {matcher: Matcher{Name: "label", Value: "val.*", IsRegex: true}, expected: true}, - {matcher: Matcher{Name: "label", Value: "diffval.*", IsRegex: true}, expected: false}, - //unset label - {matcher: Matcher{Name: "difflabel", Value: "value"}, expected: false}, - } - - lset := model.LabelSet{"label": "value"} - for _, test := range tests { - test.matcher.Init() - - actual := test.matcher.Match(lset) - require.EqualValues(t, test.expected, actual) - } -} - -func TestMatcherString(t *testing.T) { - m := NewMatcher("foo", "bar") - - if m.String() != "foo=\"bar\"" { - t.Errorf("unexpected matcher string %#v", m.String()) - } - - re, err := regexp.Compile(".*") - if err != nil { - t.Errorf("unexpected error: %s", err) - } - - m = NewRegexMatcher("foo", re) - - if m.String() != "foo=~\".*\"" { - t.Errorf("unexpected matcher string %#v", m.String()) - } -} - -func TestMatchersString(t *testing.T) { - m1 := NewMatcher("foo", "bar") - - re, err := regexp.Compile(".*") - if err != nil { - t.Errorf("unexpected error: %s", err) - } - - m2 := NewRegexMatcher("bar", re) - - matchers := NewMatchers(m1, m2) - - if matchers.String() != "{bar=~\".*\",foo=\"bar\"}" { - t.Errorf("unexpected matcher string %#v", matchers.String()) - } -} - -func TestMatchersSort(t *testing.T) { - type input struct { - name string - value string - regex bool - } - - cases := []struct { - name string - i1, i2 input - expect Matchers - }{ - { - name: "name asc order", - i1: input{name: "foo", value: "bar"}, - i2: input{name: "goo", value: "bar"}, - expect: Matchers{&Matcher{Name: "foo", Value: "bar"}, &Matcher{Name: "goo", Value: "bar"}}, - }, - { - name: "name desc order", - i1: input{name: "foo", value: "bar"}, - i2: input{name: "doo", value: "bar"}, - expect: Matchers{&Matcher{Name: "doo", Value: "bar"}, &Matcher{Name: "foo", Value: "bar"}}, - }, - { - name: "name equal", - i1: input{name: "foo", value: "bar"}, - i2: input{name: "foo", value: "bar"}, - expect: Matchers{&Matcher{Name: "foo", Value: "bar"}, &Matcher{Name: "foo", Value: "bar"}}, - }, - { - name: "type asc order", - i1: input{name: "foo", value: "bar"}, - i2: input{name: "foo", value: "car"}, - expect: Matchers{&Matcher{Name: "foo", Value: "bar"}, &Matcher{Name: "foo", Value: "car"}}, - }, - { - name: "type desc order", - i1: input{name: "foo", value: "bar"}, - i2: input{name: "foo", value: "aar"}, - expect: Matchers{&Matcher{Name: "foo", Value: "aar"}, &Matcher{Name: "foo", Value: "bar"}}, - }, - { - name: "regexp and no regexp", - i1: input{name: "foo", value: "bar"}, - i2: input{name: "foo", value: "bar", regex: true}, - expect: Matchers{&Matcher{Name: "foo", Value: "bar"}, &Matcher{Name: "foo", Value: "bar", IsRegex: true}}, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - m1 := &Matcher{Name: c.i1.name, Value: c.i1.value, IsRegex: c.i1.regex} - m1.Init() - m2 := &Matcher{Name: c.i2.name, Value: c.i2.value, IsRegex: c.i2.regex} - m2.Init() - got := NewMatchers(m1, m2) - require.True(t, equalMatchers(c.expect, got)) - }) - } -} - -func TestMatchersMatch(t *testing.T) { - - m1 := &Matcher{Name: "label1", Value: "value1"} - m1.Init() - m2 := &Matcher{Name: "label2", Value: "val.*", IsRegex: true} - m2.Init() - m3 := &Matcher{Name: "label3", Value: "value3"} - m3.Init() - - tests := []struct { - matchers Matchers - expected bool - }{ - {matchers: Matchers{m1, m2}, expected: true}, - {matchers: Matchers{m1, m3}, expected: false}, - } - - lset := model.LabelSet{"label1": "value1", "label2": "value2"} - for _, test := range tests { - actual := test.matchers.Match(lset) - require.EqualValues(t, test.expected, actual) - } -} - -func TestMatchersEqual(t *testing.T) { - - m1 := &Matcher{Name: "label1", Value: "value1"} - m1.Init() - m2 := &Matcher{Name: "label2", Value: "val.*", IsRegex: true} - m2.Init() - m3 := &Matcher{Name: "label3", Value: "value3"} - m3.Init() - - tests := []struct { - matchers1 Matchers - matchers2 Matchers - expected bool - }{ - {matchers1: Matchers{m1, m2}, matchers2: Matchers{m1, m2}, expected: true}, - {matchers1: Matchers{m1, m3}, matchers2: Matchers{m1, m2}, expected: false}, - } - - for _, test := range tests { - actual := equalMatchers(test.matchers1, test.matchers2) - require.EqualValues(t, test.expected, actual) - } -} - -func equalMatcher(m, n *Matcher) bool { - return m.Name == n.Name && m.Value == n.Value && m.IsRegex == n.IsRegex -} - -func equalMatchers(m, n Matchers) bool { - if len(m) != len(n) { - return false - } - for i, a := range m { - if !equalMatcher(a, n[i]) { - return false - } - } - return true -} diff --git a/types/types.go b/types/types.go index 26f787da..2578d0ac 100644 --- a/types/types.go +++ b/types/types.go @@ -18,6 +18,7 @@ import ( "sync" "time" + "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" ) @@ -409,7 +410,7 @@ type Silence struct { ID string `json:"id"` // A set of matchers determining if a label set is affect // by the silence. - Matchers Matchers `json:"matchers"` + Matchers labels.Matchers `json:"matchers"` // Time range of the silence. //