cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.3.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser.go

222lines · 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 recordPart = newYamlKeyValue(key, part)
110 case alertKey:
111 alertPart = newYamlKeyValue(key, part)
112 case exprKey:
113 exprPart = newPromQLExpr(key, part)
114 case forKey:
115 forPart = newYamlKeyValue(key, part)
116 case labelsKey:
117 labelsPart = newYamlMap(key, part)
118 case annotationsKey:
119 annotationsPart = newYamlMap(key, part)
120 default:
121 unknownKeys = append(unknownKeys, key)
122 }
123 }
124 }
125
126 if exprPart != nil && exprPart.Key.Position.FirstLine() != exprPart.Value.Position.FirstLine() {
127 for {
128 start := exprPart.Value.Position.FirstLine() - 1
129 end := exprPart.Value.Position.LastLine()
130 input := strings.Join(strings.Split(string(content), "\n")[start:end], "")
131 input = strings.ReplaceAll(input, " ", "")
132 output := strings.ReplaceAll(exprPart.Value.Value, "\n", "")
133 output = strings.ReplaceAll(output, " ", "")
134 if end >= len(strings.Split(string(content), "\n")) {
135 break
136 }
137 if input == output {
138 break
139 }
140 exprPart.Value.Position.Lines = append(exprPart.Value.Position.Lines, end+1)
141 }
142 }
143
144 if recordPart != nil && alertPart != nil {
145 isEmpty = false
146 rule = Rule{
147 Error: ParseError{
148 Line: node.Line,
149 Err: fmt.Errorf("got both %s and %s keys in a single rule", recordKey, alertKey),
150 },
151 }
152 return
153 }
154 if recordPart != nil && exprPart == nil {
155 isEmpty = false
156 rule = Rule{
157 Error: ParseError{
158 Line: recordPart.Key.Position.LastLine(),
159 Err: fmt.Errorf("missing %s key", exprKey),
160 },
161 }
162 return
163 }
164 if alertPart != nil && exprPart == nil {
165 isEmpty = false
166 rule = Rule{
167 Error: ParseError{
168 Line: alertPart.Key.Position.LastLine(),
169 Err: fmt.Errorf("missing %s key", exprKey),
170 },
171 }
172 return
173 }
174 if exprPart != nil && alertPart == nil && recordPart == nil {
175 isEmpty = false
176 rule = Rule{
177 Error: ParseError{
178 Line: exprPart.Key.Position.LastLine(),
179 Err: fmt.Errorf("incomplete rule, no %s or %s key", alertKey, recordKey),
180 },
181 }
182 return
183 }
184 if (recordPart != nil || alertPart != nil) && len(unknownKeys) > 0 {
185 isEmpty = false
186 var keys []string
187 for _, n := range unknownKeys {
188 keys = append(keys, n.Value)
189 }
190 rule = Rule{
191 Error: ParseError{
192 Line: unknownKeys[0].Line,
193 Err: fmt.Errorf("invalid key(s) found: %s", strings.Join(keys, ", ")),
194 },
195 }
196 return
197 }
198
199 if recordPart != nil && exprPart != nil {
200 isEmpty = false
201 rule = Rule{RecordingRule: &RecordingRule{
202 Record: *recordPart,
203 Expr: *exprPart,
204 Labels: labelsPart,
205 }}
206 return
207 }
208
209 if alertPart != nil && exprPart != nil {
210 isEmpty = false
211 rule = Rule{AlertingRule: &AlertingRule{
212 Alert: *alertPart,
213 Expr: *exprPart,
214 For: forPart,
215 Labels: labelsPart,
216 Annotations: annotationsPart,
217 }}
218 return
219 }
220
221 return
222}