cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.50.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser.go

390lines · 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 *YamlKeyValue
115 var exprPart *PromQLExpr
116 var labelsPart *YamlMap
117
118 var alertPart *YamlKeyValue
119 var forPart *YamlKeyValue
120 var keepFiringForPart *YamlKeyValue
121 var annotationsPart *YamlMap
122
123 var key *yaml.Node
124 unknownKeys := []*yaml.Node{}
125
126 var ruleComments []comments.Comment
127
128 for i, part := range unpackNodes(node) {
129 if i == 0 && node.HeadComment != "" && part.HeadComment == "" {
130 part.HeadComment = node.HeadComment
131 }
132 if i == 0 && node.LineComment != "" && part.LineComment == "" {
133 part.LineComment = node.LineComment
134 }
135 if i == len(node.Content)-1 && node.FootComment != "" && part.HeadComment == "" {
136 part.FootComment = node.FootComment
137 }
138 for _, s := range mergeComments(part) {
139 for _, c := range comments.Parse(s) {
140 if comments.IsRuleComment(c.Type) {
141 ruleComments = append(ruleComments, c)
142 }
143 }
144 }
145
146 if i%2 == 0 {
147 key = part
148 } else {
149 switch key.Value {
150 case recordKey:
151 if recordPart != nil {
152 return duplicatedKeyError(part.Line+offset, recordKey, nil)
153 }
154 recordPart = newYamlKeyValue(key, part, offset)
155 case alertKey:
156 if alertPart != nil {
157 return duplicatedKeyError(part.Line+offset, alertKey, nil)
158 }
159 alertPart = newYamlKeyValue(key, part, offset)
160 case exprKey:
161 if exprPart != nil {
162 return duplicatedKeyError(part.Line+offset, exprKey, nil)
163 }
164 exprPart = newPromQLExpr(key, part, offset)
165 case forKey:
166 if forPart != nil {
167 return duplicatedKeyError(part.Line+offset, forKey, nil)
168 }
169 forPart = newYamlKeyValue(key, part, offset)
170 case labelsKey:
171 if labelsPart != nil {
172 return duplicatedKeyError(part.Line+offset, labelsKey, nil)
173 }
174 labelsPart = newYamlMap(key, part, offset)
175 case annotationsKey:
176 if annotationsPart != nil {
177 return duplicatedKeyError(part.Line+offset, annotationsKey, nil)
178 }
179 annotationsPart = newYamlMap(key, part, offset)
180 case keepFiringForKey:
181 if keepFiringForPart != nil {
182 return duplicatedKeyError(part.Line+offset, keepFiringForKey, nil)
183 }
184 keepFiringForPart = newYamlKeyValue(key, part, offset)
185 default:
186 unknownKeys = append(unknownKeys, key)
187 }
188 }
189 }
190
191 if exprPart != nil && exprPart.Key.Position.FirstLine() != exprPart.Value.Position.FirstLine() {
192 for {
193 start := exprPart.Value.Position.FirstLine() - 1
194 end := exprPart.Value.Position.LastLine()
195 if end > len(strings.Split(string(content), "\n")) {
196 end--
197 }
198 input := strings.Join(strings.Split(string(content), "\n")[start:end], "")
199 input = strings.ReplaceAll(input, " ", "")
200 output := strings.ReplaceAll(exprPart.Value.Value, "\n", "")
201 output = strings.ReplaceAll(output, " ", "")
202 if end >= len(strings.Split(string(content), "\n")) {
203 break
204 }
205 if input == output {
206 break
207 }
208 exprPart.Value.Position.Lines = append(exprPart.Value.Position.Lines, end+1)
209 }
210 }
211
212 if recordPart != nil && alertPart != nil {
213 rule = Rule{
214 Error: ParseError{
215 Line: node.Line + offset,
216 Err: fmt.Errorf("got both %s and %s keys in a single rule", recordKey, alertKey),
217 },
218 }
219 return rule, false, err
220 }
221 if exprPart != nil && alertPart == nil && recordPart == nil {
222 rule = Rule{
223 Error: ParseError{
224 Line: exprPart.Key.Position.LastLine(),
225 Err: fmt.Errorf("incomplete rule, no %s or %s key", alertKey, recordKey),
226 },
227 }
228 return rule, false, err
229 }
230 if r, ok := ensureRequiredKeys(recordPart, exprPart); !ok {
231 return r, false, err
232 }
233 if r, ok := ensureRequiredKeys(alertPart, exprPart); !ok {
234 return r, false, err
235 }
236 if (recordPart != nil || alertPart != nil) && len(unknownKeys) > 0 {
237 var keys []string
238 for _, n := range unknownKeys {
239 keys = append(keys, n.Value)
240 }
241 rule = Rule{
242 Error: ParseError{
243 Line: unknownKeys[0].Line + offset,
244 Err: fmt.Errorf("invalid key(s) found: %s", strings.Join(keys, ", ")),
245 },
246 }
247 return rule, false, err
248 }
249
250 if recordPart != nil && exprPart != nil {
251 rule = Rule{
252 RecordingRule: &RecordingRule{
253 Record: *recordPart,
254 Expr: *exprPart,
255 Labels: labelsPart,
256 },
257 Comments: ruleComments,
258 }
259 return rule, false, err
260 }
261
262 if alertPart != nil && exprPart != nil {
263 rule = Rule{
264 AlertingRule: &AlertingRule{
265 Alert: *alertPart,
266 Expr: *exprPart,
267 For: forPart,
268 KeepFiringFor: keepFiringForPart,
269 Labels: labelsPart,
270 Annotations: annotationsPart,
271 },
272 Comments: ruleComments,
273 }
274 return rule, false, err
275 }
276
277 return rule, true, err
278}
279
280func unpackNodes(node *yaml.Node) []*yaml.Node {
281 nodes := make([]*yaml.Node, 0, len(node.Content))
282 var isMerge bool
283 for _, part := range node.Content {
284 if part.Tag == "!!merge" && part.Value == "<<" {
285 isMerge = true
286 }
287
288 if part.Alias != nil {
289 if isMerge {
290 nodes = append(nodes, resolveMapAlias(part, node).Content...)
291 } else {
292 nodes = append(nodes, resolveMapAlias(part, part))
293 }
294 isMerge = false
295 continue
296 }
297 if isMerge {
298 continue
299 }
300 nodes = append(nodes, part)
301 }
302 return nodes
303}
304
305func nodeKeys(node *yaml.Node) (keys []string) {
306 if node.Kind != yaml.MappingNode {
307 return keys
308 }
309 for i, n := range node.Content {
310 if i%2 == 0 && n.Value != "" {
311 keys = append(keys, n.Value)
312 }
313 }
314 return keys
315}
316
317func hasKey(node *yaml.Node, key string) bool {
318 for _, k := range nodeKeys(node) {
319 if k == key {
320 return true
321 }
322 }
323 return false
324}
325
326func hasValue(node *YamlNode) bool {
327 if node == nil {
328 return false
329 }
330 return node.Value != ""
331}
332
333func ensureRequiredKeys(key *YamlKeyValue, expr *PromQLExpr) (Rule, bool) {
334 if key == nil {
335 return Rule{}, true
336 }
337 if !hasValue(key.Value) {
338 return Rule{
339 Error: ParseError{
340 Line: key.Value.Position.LastLine(),
341 Err: fmt.Errorf("%s value cannot be empty", key.Key.Value),
342 },
343 }, false
344 }
345 if expr == nil {
346 return Rule{
347 Error: ParseError{
348 Line: key.Value.Position.LastLine(),
349 Err: fmt.Errorf("missing %s key", exprKey),
350 },
351 }, false
352 }
353 if !hasValue(expr.Value) {
354 return Rule{
355 Error: ParseError{
356 Line: expr.Value.Position.LastLine(),
357 Err: fmt.Errorf("%s value cannot be empty", exprKey),
358 },
359 }, false
360 }
361 return Rule{}, true
362}
363
364func resolveMapAlias(part, parent *yaml.Node) *yaml.Node {
365 node := *part
366 node.Content = nil
367 var ok bool
368 for i, alias := range part.Alias.Content {
369 if i%2 == 0 {
370 ok = !hasKey(parent, alias.Value)
371 }
372 if ok {
373 node.Content = append(node.Content, alias)
374 }
375 if i%2 == 1 {
376 ok = false
377 }
378 }
379 return &node
380}
381
382func duplicatedKeyError(line int, key string, err error) (Rule, bool, error) {
383 rule := Rule{
384 Error: ParseError{
385 Line: line,
386 Err: fmt.Errorf("duplicated %s key", key),
387 },
388 }
389 return rule, false, err
390}
391