cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.13.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser.go

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