cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
baaaa2a8a4c537acd8ab3963accdf2ef0d39534e

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/parser/parser.go

415lines · modecode

1package parser
2
3import (
4 "errors"
5 "fmt"
6 "strings"
7
8 "gopkg.in/yaml.v3"
9
10 "github.com/cloudflare/pint/internal/comments"
11)
12
13const (
14 recordKey = "record"
15 exprKey = "expr"
16 labelsKey = "labels"
17 alertKey = "alert"
18 forKey = "for"
19 keepFiringForKey = "keep_firing_for"
20 annotationsKey = "annotations"
21)
22
23var ErrRuleCommentOnFile = errors.New("this comment is only valid when attached to a rule")
24
25func NewParser() Parser {
26 return Parser{}
27}
28
29type Parser struct{}
30
31func (p Parser) Parse(content []byte) (rules []Rule, err error) {
32 if len(content) == 0 {
33 return nil, nil
34 }
35
36 defer func() {
37 if r := recover(); r != nil {
38 err = fmt.Errorf("unable to parse YAML file: %s", r)
39 }
40 }()
41
42 var node yaml.Node
43 err = yaml.Unmarshal(content, &node)
44 if err != nil {
45 return nil, err
46 }
47
48 rules, err = parseNode(content, &node, 0)
49 return rules, err
50}
51
52func parseNode(content []byte, node *yaml.Node, offset int) (rules []Rule, err error) {
53 ret, isEmpty, err := parseRule(content, node, offset)
54 if err != nil {
55 return nil, err
56 }
57 if !isEmpty {
58 rules = append(rules, ret)
59 return rules, nil
60 }
61
62 var rl []Rule
63 var rule Rule
64 for _, root := range node.Content {
65 // nolint: exhaustive
66 switch root.Kind {
67 case yaml.SequenceNode:
68 for _, n := range root.Content {
69 rl, err = parseNode(content, n, offset)
70 if err != nil {
71 return nil, err
72 }
73 rules = append(rules, rl...)
74 }
75 case yaml.MappingNode:
76 rule, isEmpty, err = parseRule(content, root, offset)
77 if err != nil {
78 return nil, err
79 }
80 if !isEmpty {
81 rules = append(rules, rule)
82 } else {
83 for _, n := range root.Content {
84 rl, err = parseNode(content, n, offset)
85 if err != nil {
86 return nil, err
87 }
88 rules = append(rules, rl...)
89 }
90 }
91 case yaml.ScalarNode:
92 if root.Value != string(content) {
93 c := []byte(root.Value)
94 var n yaml.Node
95 err = yaml.Unmarshal(c, &n)
96 if err == nil {
97 ret, err := parseNode(c, &n, offset+root.Line)
98 if err != nil {
99 return nil, err
100 }
101 rules = append(rules, ret...)
102 }
103 }
104 }
105 }
106 return rules, nil
107}
108
109func parseRule(content []byte, node *yaml.Node, offset int) (rule Rule, _ bool, err error) {
110 if node.Kind != yaml.MappingNode {
111 return rule, true, err
112 }
113
114 var recordPart *YamlNode
115 var exprPart *PromQLExpr
116 var labelsPart *YamlMap
117
118 var alertPart *YamlNode
119 var forPart *YamlNode
120 var keepFiringForPart *YamlNode
121 var annotationsPart *YamlMap
122
123 var key *yaml.Node
124 unknownKeys := []*yaml.Node{}
125
126 var lines LineRange
127
128 var ruleComments []comments.Comment
129
130 for i, part := range unpackNodes(node) {
131 if lines.First == 0 || part.Line+offset < lines.First {
132 lines.First = part.Line + offset
133 }
134 lines.Last = max(lines.Last, part.Line+offset)
135
136 if i == 0 && node.HeadComment != "" && part.HeadComment == "" {
137 part.HeadComment = node.HeadComment
138 }
139 if i == 0 && node.LineComment != "" && part.LineComment == "" {
140 part.LineComment = node.LineComment
141 }
142 if i == len(node.Content)-1 && node.FootComment != "" && part.HeadComment == "" {
143 part.FootComment = node.FootComment
144 }
145 for _, s := range mergeComments(part) {
146 for _, c := range comments.Parse(part.Line, s) {
147 if comments.IsRuleComment(c.Type) {
148 ruleComments = append(ruleComments, c)
149 }
150 }
151 }
152
153 if i%2 == 0 {
154 key = part
155 } else {
156 switch key.Value {
157 case recordKey:
158 if recordPart != nil {
159 return duplicatedKeyError(lines, part.Line+offset, recordKey, nil)
160 }
161 recordPart = newYamlNodeWithKey(key, part, offset)
162 lines.Last = max(lines.Last, recordPart.Lines.Last)
163 case alertKey:
164 if alertPart != nil {
165 return duplicatedKeyError(lines, part.Line+offset, alertKey, nil)
166 }
167 alertPart = newYamlNodeWithKey(key, part, offset)
168 lines.Last = max(lines.Last, alertPart.Lines.Last)
169 case exprKey:
170 if exprPart != nil {
171 return duplicatedKeyError(lines, part.Line+offset, exprKey, nil)
172 }
173 exprPart = newPromQLExpr(key, part, offset)
174 lines.Last = max(lines.Last, exprPart.Value.Lines.Last)
175 case forKey:
176 if forPart != nil {
177 return duplicatedKeyError(lines, part.Line+offset, forKey, nil)
178 }
179 forPart = newYamlNodeWithKey(key, part, offset)
180 lines.Last = max(lines.Last, forPart.Lines.Last)
181 case labelsKey:
182 if labelsPart != nil {
183 return duplicatedKeyError(lines, part.Line+offset, labelsKey, nil)
184 }
185 labelsPart = newYamlMap(key, part, offset)
186 lines.Last = max(lines.Last, labelsPart.Lines.Last)
187 case annotationsKey:
188 if annotationsPart != nil {
189 return duplicatedKeyError(lines, part.Line+offset, annotationsKey, nil)
190 }
191 annotationsPart = newYamlMap(key, part, offset)
192 lines.Last = max(lines.Last, annotationsPart.Lines.Last)
193
194 case keepFiringForKey:
195 if keepFiringForPart != nil {
196 return duplicatedKeyError(lines, part.Line+offset, keepFiringForKey, nil)
197 }
198 keepFiringForPart = newYamlNodeWithKey(key, part, offset)
199 lines.Last = max(lines.Last, keepFiringForPart.Lines.Last)
200 default:
201 unknownKeys = append(unknownKeys, key)
202 }
203 }
204 }
205
206 if exprPart != nil && exprPart.Value.Lines.First != exprPart.Value.Lines.Last {
207 contentLines := strings.Split(string(content), "\n")
208 for {
209 start := exprPart.Value.Lines.First
210 end := exprPart.Value.Lines.Last
211 if end > len(contentLines) {
212 end--
213 }
214 input := strings.Join(contentLines[start:end], "")
215 input = strings.ReplaceAll(input, " ", "")
216 output := strings.ReplaceAll(exprPart.Value.Value, "\n", "")
217 output = strings.ReplaceAll(output, " ", "")
218 if end >= len(contentLines) {
219 break
220 }
221 if input == output {
222 break
223 }
224 exprPart.Value.Lines.Last = end + 1
225 }
226 }
227
228 if recordPart != nil && alertPart != nil {
229 rule = Rule{
230 Lines: lines,
231 Error: ParseError{
232 Line: node.Line + offset,
233 Err: fmt.Errorf("got both %s and %s keys in a single rule", recordKey, alertKey),
234 },
235 }
236 return rule, false, err
237 }
238 if exprPart != nil && alertPart == nil && recordPart == nil {
239 rule = Rule{
240 Lines: lines,
241 Error: ParseError{
242 Line: exprPart.Value.Lines.Last,
243 Err: fmt.Errorf("incomplete rule, no %s or %s key", alertKey, recordKey),
244 },
245 }
246 return rule, false, err
247 }
248 if r, ok := ensureRequiredKeys(lines, recordKey, recordPart, exprPart); !ok {
249 return r, false, err
250 }
251 if r, ok := ensureRequiredKeys(lines, alertKey, alertPart, exprPart); !ok {
252 return r, false, err
253 }
254 if (recordPart != nil || alertPart != nil) && len(unknownKeys) > 0 {
255 var keys []string
256 for _, n := range unknownKeys {
257 keys = append(keys, n.Value)
258 }
259 rule = Rule{
260 Lines: lines,
261 Error: ParseError{
262 Line: unknownKeys[0].Line + offset,
263 Err: fmt.Errorf("invalid key(s) found: %s", strings.Join(keys, ", ")),
264 },
265 }
266 return rule, false, err
267 }
268
269 if recordPart != nil && exprPart != nil {
270 rule = Rule{
271 Lines: lines,
272 RecordingRule: &RecordingRule{
273 Record: *recordPart,
274 Expr: *exprPart,
275 Labels: labelsPart,
276 },
277 Comments: ruleComments,
278 }
279 return rule, false, err
280 }
281
282 if alertPart != nil && exprPart != nil {
283 rule = Rule{
284 Lines: lines,
285 AlertingRule: &AlertingRule{
286 Alert: *alertPart,
287 Expr: *exprPart,
288 For: forPart,
289 KeepFiringFor: keepFiringForPart,
290 Labels: labelsPart,
291 Annotations: annotationsPart,
292 },
293 Comments: ruleComments,
294 }
295 return rule, false, err
296 }
297
298 return rule, true, err
299}
300
301func unpackNodes(node *yaml.Node) []*yaml.Node {
302 nodes := make([]*yaml.Node, 0, len(node.Content))
303 var isMerge bool
304 for _, part := range node.Content {
305 if part.Tag == "!!merge" && part.Value == "<<" {
306 isMerge = true
307 }
308
309 if part.Alias != nil {
310 if isMerge {
311 nodes = append(nodes, resolveMapAlias(part, node).Content...)
312 } else {
313 nodes = append(nodes, resolveMapAlias(part, part))
314 }
315 isMerge = false
316 continue
317 }
318 if isMerge {
319 continue
320 }
321 nodes = append(nodes, part)
322 }
323 return nodes
324}
325
326func nodeKeys(node *yaml.Node) (keys []string) {
327 if node.Kind != yaml.MappingNode {
328 return keys
329 }
330 for i, n := range node.Content {
331 if i%2 == 0 && n.Value != "" {
332 keys = append(keys, n.Value)
333 }
334 }
335 return keys
336}
337
338func hasKey(node *yaml.Node, key string) bool {
339 for _, k := range nodeKeys(node) {
340 if k == key {
341 return true
342 }
343 }
344 return false
345}
346
347func hasValue(node *YamlNode) bool {
348 if node == nil {
349 return false
350 }
351 return node.Value != ""
352}
353
354func ensureRequiredKeys(lines LineRange, key string, keyVal *YamlNode, expr *PromQLExpr) (Rule, bool) {
355 if keyVal == nil {
356 return Rule{Lines: lines}, true
357 }
358 if !hasValue(keyVal) {
359 return Rule{
360 Lines: lines,
361 Error: ParseError{
362 Line: keyVal.Lines.Last,
363 Err: fmt.Errorf("%s value cannot be empty", key),
364 },
365 }, false
366 }
367 if expr == nil {
368 return Rule{
369 Lines: lines,
370 Error: ParseError{
371 Line: keyVal.Lines.Last,
372 Err: fmt.Errorf("missing %s key", exprKey),
373 },
374 }, false
375 }
376 if !hasValue(expr.Value) {
377 return Rule{
378 Lines: lines,
379 Error: ParseError{
380 Line: expr.Value.Lines.Last,
381 Err: fmt.Errorf("%s value cannot be empty", exprKey),
382 },
383 }, false
384 }
385 return Rule{Lines: lines}, true
386}
387
388func resolveMapAlias(part, parent *yaml.Node) *yaml.Node {
389 node := *part
390 node.Content = nil
391 var ok bool
392 for i, alias := range part.Alias.Content {
393 if i%2 == 0 {
394 ok = !hasKey(parent, alias.Value)
395 }
396 if ok {
397 node.Content = append(node.Content, alias)
398 }
399 if i%2 == 1 {
400 ok = false
401 }
402 }
403 return &node
404}
405
406func duplicatedKeyError(lines LineRange, line int, key string, err error) (Rule, bool, error) {
407 rule := Rule{
408 Lines: lines,
409 Error: ParseError{
410 Line: line,
411 Err: fmt.Errorf("duplicated %s key", key),
412 },
413 }
414 return rule, false, err
415}
416