cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.49.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser.go

365lines · modecode

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