cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.74.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/strict.go

286lines · modecode

1package parser
2
3import (
4 "errors"
5 "fmt"
6 "strconv"
7 "strings"
8 "time"
9
10 "github.com/prometheus/common/model"
11 "gopkg.in/yaml.v3"
12
13 "github.com/cloudflare/pint/internal/diags"
14)
15
16// https://github.com/go-yaml/yaml/blob/v3.0.1/resolve.go#L70-L81
17const (
18 nullTag = "!!null"
19 boolTag = "!!bool"
20 strTag = "!!str"
21 intTag = "!!int"
22 floatTag = "!!float"
23 timestampTag = "!!timestamp"
24 seqTag = "!!seq"
25 mapTag = "!!map"
26 binaryTag = "!!binary"
27 mergeTag = "!!merge"
28)
29
30func describeTag(tag string) string {
31 switch tag {
32 case strTag:
33 return "string"
34 case intTag:
35 return "integer"
36 case seqTag:
37 return "list"
38 case mapTag:
39 return "mapping"
40 case binaryTag:
41 return "binary data"
42 default:
43 return strings.TrimLeft(tag, "!")
44 }
45}
46
47func (p Parser) parseGroups(doc *yaml.Node, offsetLine, offsetColumn int, contentLines []string) (groups []Group, _ ParseError) {
48 names := map[string]struct{}{}
49
50 for _, node := range unpackNodes(doc) {
51 if !isTag(node.ShortTag(), mapTag) {
52 return nil, ParseError{
53 Line: node.Line,
54 Err: fmt.Errorf("top level field must be a groups key, got %s", describeTag(node.ShortTag())),
55 }
56 }
57
58 for _, entry := range mappingNodes(node) {
59 if entry.key.ShortTag() != strTag {
60 return nil, ParseError{
61 Line: entry.key.Line,
62 Err: fmt.Errorf("groups key must be a %s, got a %s", describeTag(strTag), describeTag(entry.key.ShortTag())),
63 }
64 }
65 if entry.key.Value != "groups" {
66 return nil, ParseError{
67 Line: entry.key.Line,
68 Err: fmt.Errorf("unexpected key %s", entry.key.Value),
69 }
70 }
71 if !isTag(entry.val.ShortTag(), seqTag) {
72 return nil, ParseError{
73 Line: entry.key.Line,
74 Err: fmt.Errorf("groups value must be a %s, got %s", describeTag(seqTag), describeTag(entry.val.ShortTag())),
75 }
76 }
77 for _, group := range unpackNodes(entry.val) {
78 g := p.parseGroup(group, offsetLine, offsetColumn, contentLines)
79 if _, ok := names[g.Name]; ok {
80 return nil, ParseError{
81 Line: group.Line,
82 Err: errors.New("duplicated group name"),
83 }
84 }
85 names[g.Name] = struct{}{}
86 groups = append(groups, g)
87 }
88 }
89 }
90 return groups, ParseError{}
91}
92
93func (p Parser) parseGroup(node *yaml.Node, offsetLine, offsetColumn int, contentLines []string) (group Group) {
94 if !isTag(node.ShortTag(), mapTag) {
95 group.Error = ParseError{
96 Line: node.Line,
97 Err: fmt.Errorf("group must be a %s, got %s", describeTag(mapTag), describeTag(node.ShortTag())),
98 }
99 return group
100 }
101
102 var err error
103 setKeys := make(map[string]struct{}, len(node.Content))
104
105 for _, entry := range mappingNodes(node) {
106 switch entry.key.Value {
107 case "name":
108 if entry.val.Kind != yaml.ScalarNode || entry.val.ShortTag() != strTag {
109 group.Error = ParseError{
110 Line: entry.key.Line,
111 Err: fmt.Errorf("group name must be a %s, got %s", describeTag(strTag), describeTag(entry.val.ShortTag())),
112 }
113 return group
114 }
115 if entry.val.Value == "" {
116 group.Error = ParseError{
117 Line: entry.key.Line,
118 Err: errors.New("group name cannot be empty"),
119 }
120 return group
121 }
122 group.Name = entry.val.Value
123 case "interval":
124 if entry.val.Kind != yaml.ScalarNode || entry.val.ShortTag() != strTag {
125 group.Error = ParseError{
126 Line: entry.key.Line,
127 Err: fmt.Errorf("group %s must be a %s, got %s", entry.key.Value, describeTag(strTag), describeTag(entry.val.ShortTag())),
128 }
129 return group
130 }
131 var interval model.Duration
132 if interval, err = model.ParseDuration(entry.val.Value); err != nil {
133 group.Error = ParseError{
134 Line: entry.key.Line,
135 Err: fmt.Errorf("invalid %s value: %w", entry.key.Value, err),
136 }
137 return group
138 }
139 group.Interval = time.Duration(interval)
140 case "query_offset":
141 if entry.val.Kind != yaml.ScalarNode || entry.val.ShortTag() != strTag {
142 group.Error = ParseError{
143 Line: entry.key.Line,
144 Err: fmt.Errorf("group %s must be a %s, got %s", entry.key.Value, describeTag(strTag), describeTag(entry.val.ShortTag())),
145 }
146 return group
147 }
148 var queryOffset model.Duration
149 if queryOffset, err = model.ParseDuration(entry.val.Value); err != nil {
150 group.Error = ParseError{
151 Line: entry.key.Line,
152 Err: fmt.Errorf("invalid %s value: %w", entry.key.Value, err),
153 }
154 return group
155 }
156 group.QueryOffset = time.Duration(queryOffset)
157 case "limit":
158 if entry.val.Kind != yaml.ScalarNode || entry.val.ShortTag() != intTag {
159 group.Error = ParseError{
160 Line: entry.key.Line,
161 Err: fmt.Errorf("group limit must be a %s, got %s", describeTag(intTag), describeTag(entry.val.ShortTag())),
162 }
163 return group
164 }
165 group.Limit, _ = strconv.Atoi(nodeValue(entry.val))
166 case "labels":
167 if entry.val.ShortTag() != mapTag {
168 group.Error = ParseError{
169 Line: entry.key.Line,
170 Err: fmt.Errorf("group labels must be a %s, got %s", describeTag(mapTag), describeTag(entry.val.ShortTag())),
171 }
172 return group
173 }
174 nodes := mappingNodes(entry.val)
175 if ok, err, _ := validateStringMap(
176 "labels",
177 nodes,
178 offsetLine,
179 diags.LineRange{First: entry.key.Line, Last: rangeFromYamlMaps(nodes).Last},
180 ); !ok {
181 group.Error = err
182 return group
183 }
184 group.Labels = newYamlMap(entry.key, entry.val, offsetLine, offsetColumn, contentLines)
185 case "rules":
186 if !isTag(entry.val.ShortTag(), seqTag) {
187 group.Error = ParseError{
188 Line: entry.key.Line,
189 Err: fmt.Errorf("rules must be a %s, got %s", describeTag(seqTag), describeTag(entry.val.ShortTag())),
190 }
191 return group
192 }
193 for _, rule := range unpackNodes(entry.val) {
194 group.Rules = append(group.Rules, p.parseRuleStrict(rule, contentLines))
195 }
196 case "partial_response_strategy":
197 if p.schema != ThanosSchema {
198 group.Error = ParseError{
199 Line: entry.key.Line,
200 Err: errors.New("partial_response_strategy is only valid when parser is configured to use the Thanos rule schema"),
201 }
202 return group
203 }
204 if !isTag(entry.val.ShortTag(), strTag) {
205 group.Error = ParseError{
206 Line: entry.key.Line,
207 Err: fmt.Errorf("partial_response_strategy must be a %s, got %s", describeTag(strTag), describeTag(entry.val.ShortTag())),
208 }
209 return group
210 }
211 switch val := nodeValue(entry.val); val {
212 case "warn":
213 case "abort":
214 default:
215 group.Error = ParseError{
216 Line: entry.key.Line,
217 Err: fmt.Errorf("invalid partial_response_strategy value: %s", val),
218 }
219 return group
220 }
221 default:
222 group.Error = ParseError{
223 Line: entry.key.Line,
224 Err: fmt.Errorf("invalid group key %s", entry.key.Value),
225 }
226 return group
227 }
228
229 if _, ok := setKeys[entry.key.Value]; ok {
230 group.Error = ParseError{
231 Line: entry.key.Line,
232 Err: fmt.Errorf("duplicated key %s", entry.key.Value),
233 }
234 return group
235 }
236 setKeys[entry.key.Value] = struct{}{}
237 }
238
239 if _, ok := setKeys["rules"]; ok {
240 if _, ok := setKeys["name"]; !ok {
241 group.Error = ParseError{
242 Line: node.Line,
243 Err: errors.New("incomplete group definition, name is required and must be set"),
244 }
245 return group
246 }
247 }
248
249 return group
250}
251
252func (p Parser) parseRuleStrict(rule *yaml.Node, contentLines []string) Rule {
253 if !isTag(rule.ShortTag(), mapTag) {
254 return Rule{
255 Error: ParseError{
256 Line: rule.Line,
257 Err: fmt.Errorf("rule definion must be a %s, got %s", describeTag(mapTag), describeTag(rule.ShortTag())),
258 },
259 }
260 }
261
262 for i, node := range unpackNodes(rule) {
263 if i%2 != 0 {
264 continue
265 }
266 switch node.Value {
267 case recordKey:
268 case alertKey:
269 case exprKey:
270 case forKey:
271 case keepFiringForKey:
272 case labelsKey:
273 case annotationsKey:
274 default:
275 return Rule{
276 Error: ParseError{
277 Line: node.Line,
278 Err: fmt.Errorf("invalid rule key %s", node.Value),
279 },
280 }
281 }
282 }
283
284 pr, _ := p.parseRule(rule, 0, 0, contentLines)
285 return pr
286}
287