Skip to content

Commit 353c0a1

Browse files
Support UTF-8 label matchers: Add new parser (#3453)
* Add label matchers parser This commit adds the new label matchers parser as proposed in #3353. Included is a number of compliance tests comparing the grammar supported in the new parser with the existing parser in pkg/labels. Signed-off-by: George Robinson <[email protected]> --------- Signed-off-by: George Robinson <[email protected]>
1 parent 87d3ee7 commit 353c0a1

File tree

8 files changed

+2148
-0
lines changed

8 files changed

+2148
-0
lines changed
Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
// Copyright 2023 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package compliance
15+
16+
import (
17+
"reflect"
18+
"testing"
19+
20+
"github.com/prometheus/alertmanager/matchers/parse"
21+
"github.com/prometheus/alertmanager/pkg/labels"
22+
)
23+
24+
func TestCompliance(t *testing.T) {
25+
for _, tc := range []struct {
26+
input string
27+
want labels.Matchers
28+
err string
29+
skip bool
30+
}{
31+
{
32+
input: `{}`,
33+
want: labels.Matchers{},
34+
skip: true,
35+
},
36+
{
37+
input: `{foo='}`,
38+
want: func() labels.Matchers {
39+
ms := labels.Matchers{}
40+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "'")
41+
return append(ms, m)
42+
}(),
43+
skip: true,
44+
},
45+
{
46+
input: "{foo=`}",
47+
want: func() labels.Matchers {
48+
ms := labels.Matchers{}
49+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "`")
50+
return append(ms, m)
51+
}(),
52+
skip: true,
53+
},
54+
{
55+
input: "{foo=\\\"}",
56+
want: func() labels.Matchers {
57+
ms := labels.Matchers{}
58+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "\"")
59+
return append(ms, m)
60+
}(),
61+
skip: true,
62+
},
63+
{
64+
input: `{foo=bar}`,
65+
want: func() labels.Matchers {
66+
ms := labels.Matchers{}
67+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
68+
return append(ms, m)
69+
}(),
70+
},
71+
{
72+
input: `{foo="bar"}`,
73+
want: func() labels.Matchers {
74+
ms := labels.Matchers{}
75+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
76+
return append(ms, m)
77+
}(),
78+
},
79+
{
80+
input: `{foo=~bar.*}`,
81+
want: func() labels.Matchers {
82+
ms := labels.Matchers{}
83+
m, _ := labels.NewMatcher(labels.MatchRegexp, "foo", "bar.*")
84+
return append(ms, m)
85+
}(),
86+
},
87+
{
88+
input: `{foo=~"bar.*"}`,
89+
want: func() labels.Matchers {
90+
ms := labels.Matchers{}
91+
m, _ := labels.NewMatcher(labels.MatchRegexp, "foo", "bar.*")
92+
return append(ms, m)
93+
}(),
94+
},
95+
{
96+
input: `{foo!=bar}`,
97+
want: func() labels.Matchers {
98+
ms := labels.Matchers{}
99+
m, _ := labels.NewMatcher(labels.MatchNotEqual, "foo", "bar")
100+
return append(ms, m)
101+
}(),
102+
},
103+
{
104+
input: `{foo!="bar"}`,
105+
want: func() labels.Matchers {
106+
ms := labels.Matchers{}
107+
m, _ := labels.NewMatcher(labels.MatchNotEqual, "foo", "bar")
108+
return append(ms, m)
109+
}(),
110+
},
111+
{
112+
input: `{foo!~bar.*}`,
113+
want: func() labels.Matchers {
114+
ms := labels.Matchers{}
115+
m, _ := labels.NewMatcher(labels.MatchNotRegexp, "foo", "bar.*")
116+
return append(ms, m)
117+
}(),
118+
},
119+
{
120+
input: `{foo!~"bar.*"}`,
121+
want: func() labels.Matchers {
122+
ms := labels.Matchers{}
123+
m, _ := labels.NewMatcher(labels.MatchNotRegexp, "foo", "bar.*")
124+
return append(ms, m)
125+
}(),
126+
},
127+
{
128+
input: `{foo="bar", baz!="quux"}`,
129+
want: func() labels.Matchers {
130+
ms := labels.Matchers{}
131+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
132+
m2, _ := labels.NewMatcher(labels.MatchNotEqual, "baz", "quux")
133+
return append(ms, m, m2)
134+
}(),
135+
},
136+
{
137+
input: `{foo="bar", baz!~"quux.*"}`,
138+
want: func() labels.Matchers {
139+
ms := labels.Matchers{}
140+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
141+
m2, _ := labels.NewMatcher(labels.MatchNotRegexp, "baz", "quux.*")
142+
return append(ms, m, m2)
143+
}(),
144+
},
145+
{
146+
input: `{foo="bar",baz!~".*quux", derp="wat"}`,
147+
want: func() labels.Matchers {
148+
ms := labels.Matchers{}
149+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
150+
m2, _ := labels.NewMatcher(labels.MatchNotRegexp, "baz", ".*quux")
151+
m3, _ := labels.NewMatcher(labels.MatchEqual, "derp", "wat")
152+
return append(ms, m, m2, m3)
153+
}(),
154+
},
155+
{
156+
input: `{foo="bar", baz!="quux", derp="wat"}`,
157+
want: func() labels.Matchers {
158+
ms := labels.Matchers{}
159+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
160+
m2, _ := labels.NewMatcher(labels.MatchNotEqual, "baz", "quux")
161+
m3, _ := labels.NewMatcher(labels.MatchEqual, "derp", "wat")
162+
return append(ms, m, m2, m3)
163+
}(),
164+
},
165+
{
166+
input: `{foo="bar", baz!~".*quux.*", derp="wat"}`,
167+
want: func() labels.Matchers {
168+
ms := labels.Matchers{}
169+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
170+
m2, _ := labels.NewMatcher(labels.MatchNotRegexp, "baz", ".*quux.*")
171+
m3, _ := labels.NewMatcher(labels.MatchEqual, "derp", "wat")
172+
return append(ms, m, m2, m3)
173+
}(),
174+
},
175+
{
176+
input: `{foo="bar", instance=~"some-api.*"}`,
177+
want: func() labels.Matchers {
178+
ms := labels.Matchers{}
179+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
180+
m2, _ := labels.NewMatcher(labels.MatchRegexp, "instance", "some-api.*")
181+
return append(ms, m, m2)
182+
}(),
183+
},
184+
{
185+
input: `{foo=""}`,
186+
want: func() labels.Matchers {
187+
ms := labels.Matchers{}
188+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "")
189+
return append(ms, m)
190+
}(),
191+
},
192+
{
193+
input: `{foo="bar,quux", job="job1"}`,
194+
want: func() labels.Matchers {
195+
ms := labels.Matchers{}
196+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar,quux")
197+
m2, _ := labels.NewMatcher(labels.MatchEqual, "job", "job1")
198+
return append(ms, m, m2)
199+
}(),
200+
},
201+
{
202+
input: `{foo = "bar", dings != "bums", }`,
203+
want: func() labels.Matchers {
204+
ms := labels.Matchers{}
205+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
206+
m2, _ := labels.NewMatcher(labels.MatchNotEqual, "dings", "bums")
207+
return append(ms, m, m2)
208+
}(),
209+
},
210+
{
211+
input: `foo=bar,dings!=bums`,
212+
want: func() labels.Matchers {
213+
ms := labels.Matchers{}
214+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar")
215+
m2, _ := labels.NewMatcher(labels.MatchNotEqual, "dings", "bums")
216+
return append(ms, m, m2)
217+
}(),
218+
},
219+
{
220+
input: `{quote="She said: \"Hi, ladies! That's gender-neutral…\""}`,
221+
want: func() labels.Matchers {
222+
ms := labels.Matchers{}
223+
m, _ := labels.NewMatcher(labels.MatchEqual, "quote", `She said: "Hi, ladies! That's gender-neutral…"`)
224+
return append(ms, m)
225+
}(),
226+
},
227+
{
228+
input: `statuscode=~"5.."`,
229+
want: func() labels.Matchers {
230+
ms := labels.Matchers{}
231+
m, _ := labels.NewMatcher(labels.MatchRegexp, "statuscode", "5..")
232+
return append(ms, m)
233+
}(),
234+
},
235+
{
236+
input: `tricky=~~~`,
237+
want: func() labels.Matchers {
238+
ms := labels.Matchers{}
239+
m, _ := labels.NewMatcher(labels.MatchRegexp, "tricky", "~~")
240+
return append(ms, m)
241+
}(),
242+
skip: true,
243+
},
244+
{
245+
input: `trickier==\\=\=\"`,
246+
want: func() labels.Matchers {
247+
ms := labels.Matchers{}
248+
m, _ := labels.NewMatcher(labels.MatchEqual, "trickier", `=\=\="`)
249+
return append(ms, m)
250+
}(),
251+
skip: true,
252+
},
253+
{
254+
input: `contains_quote != "\"" , contains_comma !~ "foo,bar" , `,
255+
want: func() labels.Matchers {
256+
ms := labels.Matchers{}
257+
m, _ := labels.NewMatcher(labels.MatchNotEqual, "contains_quote", `"`)
258+
m2, _ := labels.NewMatcher(labels.MatchNotRegexp, "contains_comma", "foo,bar")
259+
return append(ms, m, m2)
260+
}(),
261+
},
262+
{
263+
input: `{foo=bar}}`,
264+
want: func() labels.Matchers {
265+
ms := labels.Matchers{}
266+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar}")
267+
return append(ms, m)
268+
}(),
269+
skip: true,
270+
},
271+
{
272+
input: `{foo=bar}},}`,
273+
want: func() labels.Matchers {
274+
ms := labels.Matchers{}
275+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "bar}}")
276+
return append(ms, m)
277+
}(),
278+
skip: true,
279+
},
280+
{
281+
input: `{foo=,bar=}}`,
282+
want: func() labels.Matchers {
283+
ms := labels.Matchers{}
284+
m1, _ := labels.NewMatcher(labels.MatchEqual, "foo", "")
285+
m2, _ := labels.NewMatcher(labels.MatchEqual, "bar", "}")
286+
return append(ms, m1, m2)
287+
}(),
288+
skip: true,
289+
},
290+
{
291+
input: `job=`,
292+
want: func() labels.Matchers {
293+
m, _ := labels.NewMatcher(labels.MatchEqual, "job", "")
294+
return labels.Matchers{m}
295+
}(),
296+
skip: true,
297+
},
298+
{
299+
input: `{name-with-dashes = "bar"}`,
300+
want: func() labels.Matchers {
301+
m, _ := labels.NewMatcher(labels.MatchEqual, "name-with-dashes", "bar")
302+
return labels.Matchers{m}
303+
}(),
304+
},
305+
{
306+
input: `{,}`,
307+
err: "bad matcher format: ",
308+
},
309+
{
310+
input: `job="value`,
311+
err: `matcher value contains unescaped double quote: "value`,
312+
},
313+
{
314+
input: `job=value"`,
315+
err: `matcher value contains unescaped double quote: value"`,
316+
},
317+
{
318+
input: `trickier==\\=\=\""`,
319+
err: `matcher value contains unescaped double quote: =\\=\=\""`,
320+
},
321+
{
322+
input: `contains_unescaped_quote = foo"bar`,
323+
err: `matcher value contains unescaped double quote: foo"bar`,
324+
},
325+
{
326+
input: `{foo=~"invalid[regexp"}`,
327+
err: "error parsing regexp: missing closing ]: `[regexp)$`",
328+
},
329+
// Double escaped strings.
330+
{
331+
input: `"{foo=\"bar"}`,
332+
err: `bad matcher format: "{foo=\"bar"`,
333+
},
334+
{
335+
input: `"foo=\"bar"`,
336+
err: `bad matcher format: "foo=\"bar"`,
337+
},
338+
{
339+
input: `"foo=\"bar\""`,
340+
err: `bad matcher format: "foo=\"bar\""`,
341+
},
342+
{
343+
input: `"foo=\"bar\"`,
344+
err: `bad matcher format: "foo=\"bar\"`,
345+
},
346+
{
347+
input: `"{foo=\"bar\"}"`,
348+
err: `bad matcher format: "{foo=\"bar\"}"`,
349+
},
350+
{
351+
input: `"foo="bar""`,
352+
err: `bad matcher format: "foo="bar""`,
353+
},
354+
{
355+
input: `{{foo=`,
356+
err: `bad matcher format: {foo=`,
357+
},
358+
{
359+
input: `{foo=`,
360+
want: func() labels.Matchers {
361+
ms := labels.Matchers{}
362+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "")
363+
return append(ms, m)
364+
}(),
365+
skip: true,
366+
},
367+
{
368+
input: `{foo=}b`,
369+
want: func() labels.Matchers {
370+
ms := labels.Matchers{}
371+
m, _ := labels.NewMatcher(labels.MatchEqual, "foo", "}b")
372+
return append(ms, m)
373+
}(),
374+
skip: true,
375+
},
376+
} {
377+
t.Run(tc.input, func(t *testing.T) {
378+
if tc.skip {
379+
t.Skip()
380+
}
381+
got, err := parse.Matchers(tc.input)
382+
if err != nil && tc.err == "" {
383+
t.Fatalf("got error where none expected: %v", err)
384+
}
385+
if err == nil && tc.err != "" {
386+
t.Fatalf("expected error but got none: %v", tc.err)
387+
}
388+
if !reflect.DeepEqual(got, tc.want) {
389+
t.Fatalf("labels not equal:\ngot %#v\nwant %#v", got, tc.want)
390+
}
391+
})
392+
}
393+
}

0 commit comments

Comments
 (0)