cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.42.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser.go

340lines · 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 annotationsKey = "annotations"
17)
18
19func NewParser() Parser {
20 return Parser{}
21}
22
23type Parser struct{}
24
25func (p Parser) Parse(content []byte) (rules []Rule, err error) {
26 if len(content) == 0 {
27 return
28 }
29
30 defer func() {
31 if r := recover(); r != nil {
32 err = fmt.Errorf("unable to parse YAML file: %s", r)
33 }
34 }()
35
36 var node yaml.Node
37 err = yaml.Unmarshal(content, &node)
38 if err != nil {
39 return nil, err
40 }
41
42 return parseNode(content, &node, 0)
43}
44
45func parseNode(content []byte, node *yaml.Node, offset int) (rules []Rule, err error) {
46 ret, isEmpty, err := parseRule(content, node, offset)
47 if err != nil {
48 return nil, err
49 }
50 if !isEmpty {
51 rules = append(rules, ret)
52 return
53 }
54
55 var rl []Rule
56 var rule Rule
57 for _, root := range node.Content {
58 // nolint: exhaustive
59 switch root.Kind {
60 case yaml.SequenceNode:
61 for _, n := range root.Content {
62 rl, err = parseNode(content, n, offset)
63 if err != nil {
64 return nil, err
65 }
66 rules = append(rules, rl...)
67 }
68 case yaml.MappingNode:
69 rule, isEmpty, err = parseRule(content, root, offset)
70 if err != nil {
71 return nil, err
72 }
73 if !isEmpty {
74 rules = append(rules, rule)
75 } else {
76 for _, n := range root.Content {
77 rl, err = parseNode(content, n, offset)
78 if err != nil {
79 return nil, err
80 }
81 rules = append(rules, rl...)
82 }
83 }
84 case yaml.ScalarNode:
85 if root.Value != string(content) {
86 c := []byte(root.Value)
87 var n yaml.Node
88 err = yaml.Unmarshal(c, &n)
89 if err == nil {
90 ret, err := parseNode(c, &n, offset+root.Line)
91 if err != nil {
92 return nil, err
93 }
94 rules = append(rules, ret...)
95 }
96 }
97 }
98 }
99 return rules, nil
100}
101
102func parseRule(content []byte, node *yaml.Node, offset int) (rule Rule, isEmpty bool, err error) {
103 isEmpty = true
104
105 if node.Kind != yaml.MappingNode {
106 return
107 }
108
109 var recordPart *YamlKeyValue
110 var exprPart *PromQLExpr
111 var labelsPart *YamlMap
112
113 var alertPart *YamlKeyValue
114 var forPart *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 default:
162 unknownKeys = append(unknownKeys, key)
163 }
164 }
165 }
166
167 if exprPart != nil && exprPart.Key.Position.FirstLine() != exprPart.Value.Position.FirstLine() {
168 for {
169 start := exprPart.Value.Position.FirstLine() - 1
170 end := exprPart.Value.Position.LastLine()
171 if end > len(strings.Split(string(content), "\n")) {
172 end--
173 }
174 input := strings.Join(strings.Split(string(content), "\n")[start:end], "")
175 input = strings.ReplaceAll(input, " ", "")
176 output := strings.ReplaceAll(exprPart.Value.Value, "\n", "")
177 output = strings.ReplaceAll(output, " ", "")
178 if end >= len(strings.Split(string(content), "\n")) {
179 break
180 }
181 if input == output {
182 break
183 }
184 exprPart.Value.Position.Lines = append(exprPart.Value.Position.Lines, end+1)
185 }
186 }
187
188 if recordPart != nil && alertPart != nil {
189 isEmpty = false
190 rule = Rule{
191 Error: ParseError{
192 Line: node.Line + offset,
193 Err: fmt.Errorf("got both %s and %s keys in a single rule", recordKey, alertKey),
194 },
195 }
196 return rule, isEmpty, err
197 }
198 if recordPart != nil && exprPart == nil {
199 isEmpty = false
200 rule = Rule{
201 Error: ParseError{
202 Line: recordPart.Key.Position.LastLine(),
203 Err: fmt.Errorf("missing %s key", exprKey),
204 },
205 }
206 return rule, isEmpty, err
207 }
208 if alertPart != nil && exprPart == nil {
209 isEmpty = false
210 rule = Rule{
211 Error: ParseError{
212 Line: alertPart.Key.Position.LastLine(),
213 Err: fmt.Errorf("missing %s key", exprKey),
214 },
215 }
216 return rule, isEmpty, err
217 }
218 if exprPart != nil && alertPart == nil && recordPart == nil {
219 isEmpty = false
220 rule = Rule{
221 Error: ParseError{
222 Line: exprPart.Key.Position.LastLine(),
223 Err: fmt.Errorf("incomplete rule, no %s or %s key", alertKey, recordKey),
224 },
225 }
226 return rule, isEmpty, err
227 }
228 if (recordPart != nil || alertPart != nil) && len(unknownKeys) > 0 {
229 isEmpty = false
230 var keys []string
231 for _, n := range unknownKeys {
232 keys = append(keys, n.Value)
233 }
234 rule = Rule{
235 Error: ParseError{
236 Line: unknownKeys[0].Line + offset,
237 Err: fmt.Errorf("invalid key(s) found: %s", strings.Join(keys, ", ")),
238 },
239 }
240 return rule, isEmpty, err
241 }
242
243 if recordPart != nil && exprPart != nil {
244 isEmpty = false
245 rule = Rule{RecordingRule: &RecordingRule{
246 Record: *recordPart,
247 Expr: *exprPart,
248 Labels: labelsPart,
249 }}
250 return rule, isEmpty, err
251 }
252
253 if alertPart != nil && exprPart != nil {
254 isEmpty = false
255 rule = Rule{AlertingRule: &AlertingRule{
256 Alert: *alertPart,
257 Expr: *exprPart,
258 For: forPart,
259 Labels: labelsPart,
260 Annotations: annotationsPart,
261 }}
262 return rule, isEmpty, err
263 }
264
265 return rule, isEmpty, err
266}
267
268func unpackNodes(node *yaml.Node) []*yaml.Node {
269 nodes := make([]*yaml.Node, 0, len(node.Content))
270 var isMerge bool
271 for _, part := range node.Content {
272 if part.Tag == "!!merge" && part.Value == "<<" {
273 isMerge = true
274 }
275
276 if part.Alias != nil {
277 if isMerge {
278 nodes = append(nodes, resolveMapAlias(part, node).Content...)
279 } else {
280 nodes = append(nodes, resolveMapAlias(part, part))
281 }
282 isMerge = false
283 continue
284 }
285 if isMerge {
286 continue
287 }
288 nodes = append(nodes, part)
289 }
290 return nodes
291}
292
293func nodeKeys(node *yaml.Node) (keys []string) {
294 if node.Kind != yaml.MappingNode {
295 return keys
296 }
297 for i, n := range node.Content {
298 if i%2 == 0 && n.Value != "" {
299 keys = append(keys, n.Value)
300 }
301 }
302 return keys
303}
304
305func hasKey(node *yaml.Node, key string) bool {
306 for _, k := range nodeKeys(node) {
307 if k == key {
308 return true
309 }
310 }
311 return false
312}
313
314func resolveMapAlias(part, parent *yaml.Node) *yaml.Node {
315 node := *part
316 node.Content = nil
317 var ok bool
318 for i, alias := range part.Alias.Content {
319 if i%2 == 0 {
320 ok = !hasKey(parent, alias.Value)
321 }
322 if ok {
323 node.Content = append(node.Content, alias)
324 }
325 if i%2 == 1 {
326 ok = false
327 }
328 }
329 return &node
330}
331
332func duplicatedKeyError(line int, key string, err error) (Rule, bool, error) {
333 rule := Rule{
334 Error: ParseError{
335 Line: line,
336 Err: fmt.Errorf("duplicated %s key", key),
337 },
338 }
339 return rule, false, err
340}
341