cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.71.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/strict.go

232lines · modecode

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