cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.17.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser.go

259lines · 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)
43}
44
45func parseNode(content []byte, node *yaml.Node) (rules []Rule, err error) {
46 ret, isEmpty, err := parseRule(content, node)
47 if err != nil {
48 return nil, err
49 }
50 if !isEmpty {
51 rules = append(rules, ret)
52 return
53 }
54
55 for _, root := range node.Content {
56 switch root.Kind {
57 case yaml.SequenceNode:
58 for _, n := range root.Content {
59 ret, err := parseNode(content, n)
60 if err != nil {
61 return nil, err
62 }
63 rules = append(rules, ret...)
64 }
65 case yaml.MappingNode:
66 rule, isEmpty, err := parseRule(content, root)
67 if err != nil {
68 return nil, err
69 }
70 if !isEmpty {
71 rules = append(rules, rule)
72 } else {
73 for _, n := range root.Content {
74 ret, err := parseNode(content, n)
75 if err != nil {
76 return nil, err
77 }
78 rules = append(rules, ret...)
79 }
80 }
81 }
82 }
83 return rules, nil
84}
85
86func parseRule(content []byte, node *yaml.Node) (rule Rule, isEmpty bool, err error) {
87 isEmpty = true
88
89 if node.Kind != yaml.MappingNode {
90 return
91 }
92
93 var recordPart *YamlKeyValue
94 var exprPart *PromQLExpr
95 var labelsPart *YamlMap
96
97 var alertPart *YamlKeyValue
98 var forPart *YamlKeyValue
99 var annotationsPart *YamlMap
100
101 var key *yaml.Node
102 unknownKeys := []*yaml.Node{}
103 for i, part := range node.Content {
104 if i == 0 && node.HeadComment != "" {
105 part.HeadComment = node.HeadComment
106 }
107 if i == len(node.Content)-1 && node.FootComment != "" {
108 part.FootComment = node.FootComment
109 }
110 if i%2 == 0 {
111 key = part
112 } else {
113 switch key.Value {
114 case recordKey:
115 if recordPart != nil {
116 return duplicatedKeyError(part.Line, recordKey, nil)
117 }
118 recordPart = newYamlKeyValue(key, part)
119 case alertKey:
120 if alertPart != nil {
121 return duplicatedKeyError(part.Line, alertKey, nil)
122 }
123 alertPart = newYamlKeyValue(key, part)
124 case exprKey:
125 if exprPart != nil {
126 return duplicatedKeyError(part.Line, exprKey, nil)
127 }
128 exprPart = newPromQLExpr(key, part)
129 case forKey:
130 if forPart != nil {
131 return duplicatedKeyError(part.Line, forKey, nil)
132 }
133 forPart = newYamlKeyValue(key, part)
134 case labelsKey:
135 if labelsPart != nil {
136 return duplicatedKeyError(part.Line, labelsKey, nil)
137 }
138 labelsPart = newYamlMap(key, part)
139 case annotationsKey:
140 if annotationsPart != nil {
141 return duplicatedKeyError(part.Line, annotationsKey, nil)
142 }
143 annotationsPart = newYamlMap(key, part)
144 default:
145 unknownKeys = append(unknownKeys, key)
146 }
147 }
148 }
149
150 if exprPart != nil && exprPart.Key.Position.FirstLine() != exprPart.Value.Position.FirstLine() {
151 for {
152 start := exprPart.Value.Position.FirstLine() - 1
153 end := exprPart.Value.Position.LastLine()
154 if end > len(strings.Split(string(content), "\n")) {
155 end--
156 }
157 input := strings.Join(strings.Split(string(content), "\n")[start:end], "")
158 input = strings.ReplaceAll(input, " ", "")
159 output := strings.ReplaceAll(exprPart.Value.Value, "\n", "")
160 output = strings.ReplaceAll(output, " ", "")
161 if end >= len(strings.Split(string(content), "\n")) {
162 break
163 }
164 if input == output {
165 break
166 }
167 exprPart.Value.Position.Lines = append(exprPart.Value.Position.Lines, end+1)
168 }
169 }
170
171 if recordPart != nil && alertPart != nil {
172 isEmpty = false
173 rule = Rule{
174 Error: ParseError{
175 Line: node.Line,
176 Err: fmt.Errorf("got both %s and %s keys in a single rule", recordKey, alertKey),
177 },
178 }
179 return
180 }
181 if recordPart != nil && exprPart == nil {
182 isEmpty = false
183 rule = Rule{
184 Error: ParseError{
185 Line: recordPart.Key.Position.LastLine(),
186 Err: fmt.Errorf("missing %s key", exprKey),
187 },
188 }
189 return
190 }
191 if alertPart != nil && exprPart == nil {
192 isEmpty = false
193 rule = Rule{
194 Error: ParseError{
195 Line: alertPart.Key.Position.LastLine(),
196 Err: fmt.Errorf("missing %s key", exprKey),
197 },
198 }
199 return
200 }
201 if exprPart != nil && alertPart == nil && recordPart == nil {
202 isEmpty = false
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
210 }
211 if (recordPart != nil || alertPart != nil) && len(unknownKeys) > 0 {
212 isEmpty = false
213 var keys []string
214 for _, n := range unknownKeys {
215 keys = append(keys, n.Value)
216 }
217 rule = Rule{
218 Error: ParseError{
219 Line: unknownKeys[0].Line,
220 Err: fmt.Errorf("invalid key(s) found: %s", strings.Join(keys, ", ")),
221 },
222 }
223 return
224 }
225
226 if recordPart != nil && exprPart != nil {
227 isEmpty = false
228 rule = Rule{RecordingRule: &RecordingRule{
229 Record: *recordPart,
230 Expr: *exprPart,
231 Labels: labelsPart,
232 }}
233 return
234 }
235
236 if alertPart != nil && exprPart != nil {
237 isEmpty = false
238 rule = Rule{AlertingRule: &AlertingRule{
239 Alert: *alertPart,
240 Expr: *exprPart,
241 For: forPart,
242 Labels: labelsPart,
243 Annotations: annotationsPart,
244 }}
245 return
246 }
247
248 return
249}
250
251func duplicatedKeyError(line int, key string, err error) (Rule, bool, error) {
252 rule := Rule{
253 Error: ParseError{
254 Line: line,
255 Err: fmt.Errorf("duplicated %s key", key),
256 },
257 }
258 return rule, false, err
259}