cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.84.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/parser.go

707lines · modeblame

a68c8b60Lukasz Mierzwa5 years ago1package parser
2
3import (
37a97b61Lukasz Mierzwa2 years ago4"errors"
a68c8b60Lukasz Mierzwa5 years ago5"fmt"
28bb4100Felipe Sere2 years ago6"io"
f9e7354aLukasz Mierzwa2 years ago7"regexp"
8"strconv"
a68c8b60Lukasz Mierzwa5 years ago9"strings"
10
77425611Lukasz Mierzwa11 months ago11"go.yaml.in/yaml/v3"
37a97b61Lukasz Mierzwa2 years ago12
a138844fLukasz Mierzwa2 years ago13"github.com/prometheus/common/model"
14
37a97b61Lukasz Mierzwa2 years ago15"github.com/cloudflare/pint/internal/comments"
3002aa5eLukasz Mierzwa1 years ago16"github.com/cloudflare/pint/internal/diags"
a68c8b60Lukasz Mierzwa5 years ago17)
18
19const (
bb2a6615Felipe Sere2 years ago20recordKey = "record"
21exprKey = "expr"
22labelsKey = "labels"
23alertKey = "alert"
24forKey = "for"
25keepFiringForKey = "keep_firing_for"
26annotationsKey = "annotations"
a68c8b60Lukasz Mierzwa5 years ago27)
28
37a97b61Lukasz Mierzwa2 years ago29var ErrRuleCommentOnFile = errors.New("this comment is only valid when attached to a rule")
30
ee682eb6Lukasz Mierzwa1 years ago31type Schema uint8
468f7288Lukasz Mierzwa1 years ago32
33const (
34PrometheusSchema Schema = iota
35ThanosSchema
36)
37
30d99502Lukasz Mierzwa4 months ago38type Options struct {
39Names model.ValidationScheme
40Schema Schema
41IsStrict bool
42}
43
44var DefaultOptions = Options{
45Names: model.UTF8Validation,
46Schema: PrometheusSchema,
47IsStrict: false,
48}
49
50func (o Options) WithStrict(strict bool) Options {
51o.IsStrict = strict
52return o
53}
54
55func NewParser(opts Options) Parser {
56if opts.Names == model.UnsetValidation {
57opts.Names = model.LegacyValidation
f9e7354aLukasz Mierzwa2 years ago58}
30d99502Lukasz Mierzwa4 months ago59return Parser{opts: opts}
a68c8b60Lukasz Mierzwa5 years ago60}
61
f9e7354aLukasz Mierzwa2 years ago62type Parser struct {
30d99502Lukasz Mierzwa4 months ago63opts Options
f9e7354aLukasz Mierzwa2 years ago64}
a68c8b60Lukasz Mierzwa5 years ago65
6c8c0736Lukasz Mierzwa1 years ago66func (p Parser) Parse(src io.Reader) (f File) {
67cr := newContentReader(src)
7b1f5613Lukasz Mierzwa1 years ago68dec := yaml.NewDecoder(cr)
dc0f7dd0Lukasz Mierzwa1 years ago69
6c8c0736Lukasz Mierzwa1 years ago70defer func() {
71f.Diagnostics = cr.diagnostics
72f.Comments = cr.comments
73f.TotalLines = cr.lineno
74}()
75
30d99502Lukasz Mierzwa4 months ago76f.IsRelaxed = !p.opts.IsStrict
dc0f7dd0Lukasz Mierzwa1 years ago77
f9e7354aLukasz Mierzwa2 years ago78var index int
0853ee73Lukasz Mierzwa1 years ago79var g []Group
28bb4100Felipe Sere2 years ago80for {
81var doc yaml.Node
82decodeErr := dec.Decode(&doc)
83if errors.Is(decodeErr, io.EOF) {
84break
85}
dc0f7dd0Lukasz Mierzwa1 years ago86
28bb4100Felipe Sere2 years ago87if decodeErr != nil {
0853ee73Lukasz Mierzwa1 years ago88f.Error = tryDecodingYamlError(decodeErr)
6c8c0736Lukasz Mierzwa1 years ago89return f
f9e7354aLukasz Mierzwa2 years ago90}
91index++
dc0f7dd0Lukasz Mierzwa1 years ago92
30d99502Lukasz Mierzwa4 months ago93if p.opts.IsStrict {
f9f9f6c2Lukasz Mierzwa1 years ago94g, f.Error = p.parseGroups(&doc, 0, 0, cr.lines)
0853ee73Lukasz Mierzwa1 years ago95if f.Error.Err != nil {
6c8c0736Lukasz Mierzwa1 years ago96return f
f9e7354aLukasz Mierzwa2 years ago97}
0853ee73Lukasz Mierzwa1 years ago98f.Groups = append(f.Groups, g...)
f9e7354aLukasz Mierzwa2 years ago99} else {
dc0f7dd0Lukasz Mierzwa1 years ago100f.Groups = append(f.Groups, p.parseNode(&doc, nil, nil, 0, 0, cr.lines)...)
f9e7354aLukasz Mierzwa2 years ago101}
dc0f7dd0Lukasz Mierzwa1 years ago102
30d99502Lukasz Mierzwa4 months ago103if index > 1 && p.opts.IsStrict {
0853ee73Lukasz Mierzwa1 years ago104f.Error = ParseError{
105Err: errors.New("multi-document YAML files are not allowed"),
106Details: `This is a multi-document YAML file. Prometheus will only parse the first document and silently ignore the rest.
f9e7354aLukasz Mierzwa2 years ago107To allow for multi-document YAML files set parser->relaxed option in pint config file.`,
0853ee73Lukasz Mierzwa1 years ago108Line: doc.Line,
109}
28bb4100Felipe Sere2 years ago110}
a68c8b60Lukasz Mierzwa5 years ago111}
6c8c0736Lukasz Mierzwa1 years ago112return f
a68c8b60Lukasz Mierzwa5 years ago113}
114
dc0f7dd0Lukasz Mierzwa1 years ago115func (p *Parser) parseNode(node, parent *yaml.Node, group *Group, offsetLine, offsetColumn int, contentLines []string) (groups []Group) {
116switch node.Kind { // nolint: exhaustive
117case yaml.SequenceNode:
5d6f2e93Lukasz Mierzwa1 years ago118// First check for group list
119for _, n := range unpackNodes(node) {
120if g, rulesMap, ok := tryParseGroup(n, offsetLine, offsetColumn, contentLines); ok {
121groups = append(groups, p.parseNode(rulesMap.val, rulesMap.key, &g, offsetLine, offsetColumn, contentLines)...)
dc0f7dd0Lukasz Mierzwa1 years ago122}
5d6f2e93Lukasz Mierzwa1 years ago123}
124if len(groups) > 0 {
dc0f7dd0Lukasz Mierzwa1 years ago125return groups
126}
5d6f2e93Lukasz Mierzwa1 years ago127
dc0f7dd0Lukasz Mierzwa1 years ago128if group == nil {
129group = &Group{} // nolint: exhaustruct
130}
5d6f2e93Lukasz Mierzwa1 years ago131// Try parsing rules.
dc0f7dd0Lukasz Mierzwa1 years ago132for _, n := range unpackNodes(node) {
f9f9f6c2Lukasz Mierzwa1 years ago133if ret, isEmpty := p.parseRule(n, offsetLine, offsetColumn, contentLines); !isEmpty {
dc0f7dd0Lukasz Mierzwa1 years ago134group.Rules = append(group.Rules, ret)
135}
136}
137// Handle empty rules within a group.
138if len(group.Rules) > 0 || (parent != nil && nodeValue(parent) == "rules" && len(groups) == 0 && group != nil) {
139groups = append(groups, *group)
140}
5d6f2e93Lukasz Mierzwa1 years ago141if len(groups) > 0 {
142return groups
143}
dc0f7dd0Lukasz Mierzwa1 years ago144case yaml.MappingNode:
145for _, field := range mappingNodes(node) {
146groups = append(groups, p.parseNode(field.val, field.key, group, offsetLine, offsetColumn, contentLines)...)
147}
148return groups
149case yaml.ScalarNode:
150if strings.Count(node.Value, "\n") > 1 && node.Value != strings.Join(contentLines, "\n") && node.Line < len(contentLines) {
151var n yaml.Node
152// FIXME there must be a better way.
153// If we have YAML inside YAML:
154// alerts: |
155// groups:
156// rules: ...
157// Then we need to get the offset of `groups` inside the FILE, not inside the YAML node value.
158// Right now we read the line where it's in the file and count leading spaces.
159if err := yaml.Unmarshal([]byte(node.Value), &n); err == nil {
e7a03d38Lukasz Mierzwa3 months ago160groups = append(
161groups,
dc0f7dd0Lukasz Mierzwa1 years ago162p.parseNode(
163&n,
164node,
165group,
166offsetLine+node.Line,
167offsetColumn+countLeadingSpace(contentLines[node.Line]),
168strings.Split(node.Value, "\n"),
169)...,
170)
171return groups
172}
173}
174}
175
176for _, child := range unpackNodes(node) {
177groups = append(groups, p.parseNode(child, node, group, offsetLine, offsetColumn, contentLines)...)
a68c8b60Lukasz Mierzwa5 years ago178}
179
dc0f7dd0Lukasz Mierzwa1 years ago180return groups
181}
182
183func tryParseGroup(node *yaml.Node, offsetLine, offsetColumn int, contentLines []string) (g Group, rules yamlMap, _ bool) {
184for _, e := range mappingNodes(node) {
185switch val := nodeValue(e.key); val {
186case "name":
bef2454dLukasz Mierzwa4 months ago187g.Name = YamlNode{
188Value: nodeValue(e.val),
189Pos: diags.NewPositionRange(contentLines, e.val, 1),
190}
dc0f7dd0Lukasz Mierzwa1 years ago191case "interval":
f3d6bdd2Lukasz Mierzwa4 months ago192g.Interval = newYamlDuration(e.val, offsetLine, offsetColumn, contentLines, 1)
dc0f7dd0Lukasz Mierzwa1 years ago193case "limit":
bef2454dLukasz Mierzwa4 months ago194g.Limit = newYamlInt(e.val, offsetLine, offsetColumn, contentLines, 1)
dc0f7dd0Lukasz Mierzwa1 years ago195case "query_offset":
f3d6bdd2Lukasz Mierzwa4 months ago196g.QueryOffset = newYamlDuration(e.val, offsetLine, offsetColumn, contentLines, 1)
dc0f7dd0Lukasz Mierzwa1 years ago197case "labels":
198g.Labels = newYamlMap(e.key, e.val, offsetLine, offsetColumn, contentLines)
199case "rules":
200if e.val.Kind == yaml.SequenceNode {
201rules = e
a68c8b60Lukasz Mierzwa5 years ago202}
203}
204}
bef2454dLukasz Mierzwa4 months ago205return g, rules, g.Name.Value != "" && rules.key != nil
a68c8b60Lukasz Mierzwa5 years ago206}
207
f9f9f6c2Lukasz Mierzwa1 years ago208func (p Parser) parseRule(node *yaml.Node, offsetLine, offsetColumn int, contentLines []string) (rule Rule, _ bool) {
3c8a7f4bLukasz Mierzwa2 years ago209var recordPart *YamlNode
a68c8b60Lukasz Mierzwa5 years ago210var exprPart *PromQLExpr
211var labelsPart *YamlMap
212
3c8a7f4bLukasz Mierzwa2 years ago213var alertPart *YamlNode
f3d6bdd2Lukasz Mierzwa4 months ago214var forPart *YamlDuration
215var keepFiringForPart *YamlDuration
a68c8b60Lukasz Mierzwa5 years ago216var annotationsPart *YamlMap
217
1453b819Lukasz Mierzwa2 years ago218var recordNode *yaml.Node
219var alertNode *yaml.Node
220var exprNode *yaml.Node
221var forNode *yaml.Node
222var keepFiringForNode *yaml.Node
223var labelsNode *yaml.Node
224var annotationsNode *yaml.Node
468f7288Lukasz Mierzwa1 years ago225
f9e7354aLukasz Mierzwa2 years ago226labelsNodes := []yamlMap{}
227annotationsNodes := []yamlMap{}
1453b819Lukasz Mierzwa2 years ago228
a68c8b60Lukasz Mierzwa5 years ago229var key *yaml.Node
230unknownKeys := []*yaml.Node{}
3e11b598Lukasz Mierzwa4 years ago231
3002aa5eLukasz Mierzwa1 years ago232var lines diags.LineRange
26ebaab1Lukasz Mierzwa2 years ago233
37a97b61Lukasz Mierzwa2 years ago234var ruleComments []comments.Comment
eef6745bLukasz Mierzwa2 years ago235
3e11b598Lukasz Mierzwa4 years ago236for i, part := range unpackNodes(node) {
636c2a98Lukasz Mierzwa1 years ago237if lines.First == 0 || part.Line+offsetLine < lines.First {
238lines.First = part.Line + offsetLine
26ebaab1Lukasz Mierzwa2 years ago239}
636c2a98Lukasz Mierzwa1 years ago240lines.Last = max(lines.Last, part.Line+offsetLine)
26ebaab1Lukasz Mierzwa2 years ago241
eef6745bLukasz Mierzwa2 years ago242if i == 0 && node.HeadComment != "" && part.HeadComment == "" {
032875e9Lukasz Mierzwa5 years ago243part.HeadComment = node.HeadComment
244}
37a97b61Lukasz Mierzwa2 years ago245if i == 0 && node.LineComment != "" && part.LineComment == "" {
246part.LineComment = node.LineComment
247}
eef6745bLukasz Mierzwa2 years ago248if i == len(node.Content)-1 && node.FootComment != "" && part.HeadComment == "" {
032875e9Lukasz Mierzwa5 years ago249part.FootComment = node.FootComment
250}
8fb2be3dLukasz Mierzwa3 months ago251for _, ct := range mergeComments(part, contentLines) {
252for _, c := range comments.Parse(ct.line, ct.text, ct.column) {
37a97b61Lukasz Mierzwa2 years ago253if comments.IsRuleComment(c.Type) {
254ruleComments = append(ruleComments, c)
255}
256}
257}
eef6745bLukasz Mierzwa2 years ago258
a68c8b60Lukasz Mierzwa5 years ago259if i%2 == 0 {
260key = part
261} else {
262switch key.Value {
263case recordKey:
14ab07ffLukasz Mierzwa4 years ago264if recordPart != nil {
636c2a98Lukasz Mierzwa1 years ago265return duplicatedKeyError(lines, part.Line+offsetLine, recordKey)
14ab07ffLukasz Mierzwa4 years ago266}
1453b819Lukasz Mierzwa2 years ago267recordNode = part
73d0e49eLukasz Mierzwa1 years ago268recordPart = newYamlNode(part, offsetLine, offsetColumn, contentLines, key.Column+2)
a0a4e8b3Lukasz Mierzwa1 years ago269lines.Last = max(lines.Last, recordPart.Pos.Lines().Last)
a68c8b60Lukasz Mierzwa5 years ago270case alertKey:
14ab07ffLukasz Mierzwa4 years ago271if alertPart != nil {
636c2a98Lukasz Mierzwa1 years ago272return duplicatedKeyError(lines, part.Line+offsetLine, alertKey)
14ab07ffLukasz Mierzwa4 years ago273}
1453b819Lukasz Mierzwa2 years ago274alertNode = part
73d0e49eLukasz Mierzwa1 years ago275alertPart = newYamlNode(part, offsetLine, offsetColumn, contentLines, key.Column+2)
a0a4e8b3Lukasz Mierzwa1 years ago276lines.Last = max(lines.Last, alertPart.Pos.Lines().Last)
a68c8b60Lukasz Mierzwa5 years ago277case exprKey:
14ab07ffLukasz Mierzwa4 years ago278if exprPart != nil {
636c2a98Lukasz Mierzwa1 years ago279return duplicatedKeyError(lines, part.Line+offsetLine, exprKey)
14ab07ffLukasz Mierzwa4 years ago280}
1453b819Lukasz Mierzwa2 years ago281exprNode = part
73d0e49eLukasz Mierzwa1 years ago282exprPart = newPromQLExpr(part, offsetLine, offsetColumn, contentLines, key.Column+2)
a0a4e8b3Lukasz Mierzwa1 years ago283lines.Last = max(lines.Last, exprPart.Value.Pos.Lines().Last)
a68c8b60Lukasz Mierzwa5 years ago284case forKey:
14ab07ffLukasz Mierzwa4 years ago285if forPart != nil {
636c2a98Lukasz Mierzwa1 years ago286return duplicatedKeyError(lines, part.Line+offsetLine, forKey)
14ab07ffLukasz Mierzwa4 years ago287}
1453b819Lukasz Mierzwa2 years ago288forNode = part
f3d6bdd2Lukasz Mierzwa4 months ago289forPart = newYamlDuration(part, offsetLine, offsetColumn, contentLines, key.Column+2)
a0a4e8b3Lukasz Mierzwa1 years ago290lines.Last = max(lines.Last, forPart.Pos.Lines().Last)
1453b819Lukasz Mierzwa2 years ago291case keepFiringForKey:
292if keepFiringForPart != nil {
636c2a98Lukasz Mierzwa1 years ago293return duplicatedKeyError(lines, part.Line+offsetLine, keepFiringForKey)
1453b819Lukasz Mierzwa2 years ago294}
295keepFiringForNode = part
f3d6bdd2Lukasz Mierzwa4 months ago296keepFiringForPart = newYamlDuration(part, offsetLine, offsetColumn, contentLines, key.Column+2)
a0a4e8b3Lukasz Mierzwa1 years ago297lines.Last = max(lines.Last, keepFiringForPart.Pos.Lines().Last)
a68c8b60Lukasz Mierzwa5 years ago298case labelsKey:
14ab07ffLukasz Mierzwa4 years ago299if labelsPart != nil {
636c2a98Lukasz Mierzwa1 years ago300return duplicatedKeyError(lines, part.Line+offsetLine, labelsKey)
14ab07ffLukasz Mierzwa4 years ago301}
1453b819Lukasz Mierzwa2 years ago302labelsNode = part
303labelsNodes = mappingNodes(part)
73d0e49eLukasz Mierzwa1 years ago304labelsPart = newYamlMap(key, part, offsetLine, offsetColumn, contentLines)
a0a4e8b3Lukasz Mierzwa1 years ago305lines.Last = max(lines.Last, labelsPart.Lines().Last)
a68c8b60Lukasz Mierzwa5 years ago306case annotationsKey:
14ab07ffLukasz Mierzwa4 years ago307if annotationsPart != nil {
636c2a98Lukasz Mierzwa1 years ago308return duplicatedKeyError(lines, part.Line+offsetLine, annotationsKey)
14ab07ffLukasz Mierzwa4 years ago309}
1453b819Lukasz Mierzwa2 years ago310annotationsNode = part
311annotationsNodes = mappingNodes(part)
73d0e49eLukasz Mierzwa1 years ago312annotationsPart = newYamlMap(key, part, offsetLine, offsetColumn, contentLines)
a0a4e8b3Lukasz Mierzwa1 years ago313lines.Last = max(lines.Last, annotationsPart.Lines().Last)
a68c8b60Lukasz Mierzwa5 years ago314default:
315unknownKeys = append(unknownKeys, key)
316}
317}
318}
319
320if recordPart != nil && alertPart != nil {
321rule = Rule{
26ebaab1Lukasz Mierzwa2 years ago322Lines: lines,
a68c8b60Lukasz Mierzwa5 years ago323Error: ParseError{
636c2a98Lukasz Mierzwa1 years ago324Line: node.Line + offsetLine,
a68c8b60Lukasz Mierzwa5 years ago325Err: fmt.Errorf("got both %s and %s keys in a single rule", recordKey, alertKey),
326},
327}
990289beLukasz Mierzwa2 years ago328return rule, false
a68c8b60Lukasz Mierzwa5 years ago329}
330if exprPart != nil && alertPart == nil && recordPart == nil {
331rule = Rule{
26ebaab1Lukasz Mierzwa2 years ago332Lines: lines,
a68c8b60Lukasz Mierzwa5 years ago333Error: ParseError{
a0a4e8b3Lukasz Mierzwa1 years ago334Line: exprPart.Value.Pos.Lines().Last,
a68c8b60Lukasz Mierzwa5 years ago335Err: fmt.Errorf("incomplete rule, no %s or %s key", alertKey, recordKey),
336},
337}
990289beLukasz Mierzwa2 years ago338return rule, false
4b4ce464Lukasz Mierzwa3 years ago339}
f3d6bdd2Lukasz Mierzwa4 months ago340if recordPart != nil && forNode != nil {
a138844fLukasz Mierzwa2 years ago341rule = Rule{
342Lines: lines,
343Error: ParseError{
f3d6bdd2Lukasz Mierzwa4 months ago344Line: forNode.Line + offsetLine,
a138844fLukasz Mierzwa2 years ago345Err: fmt.Errorf("invalid field '%s' in recording rule", forKey),
346},
347}
990289beLukasz Mierzwa2 years ago348return rule, false
a138844fLukasz Mierzwa2 years ago349}
f3d6bdd2Lukasz Mierzwa4 months ago350if recordPart != nil && keepFiringForNode != nil {
a138844fLukasz Mierzwa2 years ago351rule = Rule{
352Lines: lines,
353Error: ParseError{
f3d6bdd2Lukasz Mierzwa4 months ago354Line: keepFiringForNode.Line + offsetLine,
a138844fLukasz Mierzwa2 years ago355Err: fmt.Errorf("invalid field '%s' in recording rule", keepFiringForKey),
356},
357}
990289beLukasz Mierzwa2 years ago358return rule, false
a138844fLukasz Mierzwa2 years ago359}
360if recordPart != nil && annotationsPart != nil {
361rule = Rule{
362Lines: lines,
363Error: ParseError{
a0a4e8b3Lukasz Mierzwa1 years ago364Line: annotationsPart.Lines().First,
a138844fLukasz Mierzwa2 years ago365Err: fmt.Errorf("invalid field '%s' in recording rule", annotationsKey),
366},
367}
990289beLukasz Mierzwa2 years ago368return rule, false
a138844fLukasz Mierzwa2 years ago369}
f9e7354aLukasz Mierzwa2 years ago370for _, entry := range []struct {
371part *yaml.Node
372key string
373}{
374{key: recordKey, part: recordNode},
375{key: alertKey, part: alertNode},
376{key: exprKey, part: exprNode},
377{key: forKey, part: forNode},
378{key: keepFiringForKey, part: keepFiringForNode},
1f317ebbLukasz Mierzwa2 years ago379} {
f9e7354aLukasz Mierzwa2 years ago380if entry.part != nil && !isTag(entry.part.ShortTag(), strTag) {
636c2a98Lukasz Mierzwa1 years ago381return invalidValueError(lines, entry.part.Line+offsetLine, entry.key, describeTag(strTag), describeTag(entry.part.ShortTag()))
1f317ebbLukasz Mierzwa2 years ago382}
383}
384
9cbd2775Lukasz Mierzwa9 months ago385if (recordPart != nil || alertPart != nil) && exprPart != nil && labelsNode != nil && !isTag(labelsNode.ShortTag(), mapTag) {
386return invalidValueError(lines, labelsNode.Line+offsetLine, labelsKey, describeTag(mapTag), describeTag(labelsNode.ShortTag()))
387}
388
389if alertPart != nil && exprPart != nil && annotationsNode != nil && !isTag(annotationsNode.ShortTag(), mapTag) {
390return invalidValueError(lines, annotationsNode.Line+offsetLine, annotationsKey, describeTag(mapTag), describeTag(annotationsNode.ShortTag()))
1f317ebbLukasz Mierzwa2 years ago391}
392
7732eaabLukasz Mierzwa1 years ago393if ok, perr, plines := validateStringMap(labelsKey, labelsNodes, offsetLine, lines); !ok {
394return Rule{
395Lines: plines,
396Error: perr,
397}, false
398}
399
400if ok, perr, plines := validateStringMap(annotationsKey, annotationsNodes, offsetLine, lines); !ok {
401return Rule{
402Lines: plines,
403Error: perr,
404}, false
1f317ebbLukasz Mierzwa2 years ago405}
406
3c8a7f4bLukasz Mierzwa2 years ago407if r, ok := ensureRequiredKeys(lines, recordKey, recordPart, exprPart); !ok {
990289beLukasz Mierzwa2 years ago408return r, false
4b4ce464Lukasz Mierzwa3 years ago409}
3c8a7f4bLukasz Mierzwa2 years ago410if r, ok := ensureRequiredKeys(lines, alertKey, alertPart, exprPart); !ok {
990289beLukasz Mierzwa2 years ago411return r, false
a68c8b60Lukasz Mierzwa5 years ago412}
413if (recordPart != nil || alertPart != nil) && len(unknownKeys) > 0 {
414var keys []string
415for _, n := range unknownKeys {
416keys = append(keys, n.Value)
417}
418rule = Rule{
26ebaab1Lukasz Mierzwa2 years ago419Lines: lines,
a68c8b60Lukasz Mierzwa5 years ago420Error: ParseError{
636c2a98Lukasz Mierzwa1 years ago421Line: unknownKeys[0].Line + offsetLine,
a68c8b60Lukasz Mierzwa5 years ago422Err: fmt.Errorf("invalid key(s) found: %s", strings.Join(keys, ", ")),
423},
424}
990289beLukasz Mierzwa2 years ago425return rule, false
a68c8b60Lukasz Mierzwa5 years ago426}
427
30d99502Lukasz Mierzwa4 months ago428if recordPart != nil && !p.opts.Names.IsValidMetricName(recordPart.Value) {
a138844fLukasz Mierzwa2 years ago429return Rule{
430Lines: lines,
431Error: ParseError{
a0a4e8b3Lukasz Mierzwa1 years ago432Line: recordPart.Pos.Lines().First,
a138844fLukasz Mierzwa2 years ago433Err: fmt.Errorf("invalid recording rule name: %s", recordPart.Value),
434},
990289beLukasz Mierzwa2 years ago435}, false
a138844fLukasz Mierzwa2 years ago436}
437
438if (recordPart != nil || alertPart != nil) && labelsPart != nil {
439for _, lab := range labelsPart.Items {
30d99502Lukasz Mierzwa4 months ago440if !p.opts.Names.IsValidLabelName(lab.Key.Value) || lab.Key.Value == model.MetricNameLabel {
a138844fLukasz Mierzwa2 years ago441return Rule{
442Lines: lines,
443Error: ParseError{
a0a4e8b3Lukasz Mierzwa1 years ago444Line: lab.Key.Pos.Lines().First,
a138844fLukasz Mierzwa2 years ago445Err: fmt.Errorf("invalid label name: %s", lab.Key.Value),
446},
990289beLukasz Mierzwa2 years ago447}, false
a138844fLukasz Mierzwa2 years ago448}
dcb7e3a8Lukasz Mierzwa8 months ago449// We don't validate labe values because they would only fail on invalid UTF-8.
450// But YAML parsing already validates UTF-8 for us and we would get an error
451// if we tried to parse file with invalid content.
a138844fLukasz Mierzwa2 years ago452}
453}
454
455if alertPart != nil && annotationsPart != nil {
456for _, ann := range annotationsPart.Items {
30d99502Lukasz Mierzwa4 months ago457if !p.opts.Names.IsValidLabelName(ann.Key.Value) {
a138844fLukasz Mierzwa2 years ago458return Rule{
459Lines: lines,
460Error: ParseError{
a0a4e8b3Lukasz Mierzwa1 years ago461Line: ann.Key.Pos.Lines().First,
a138844fLukasz Mierzwa2 years ago462Err: fmt.Errorf("invalid annotation name: %s", ann.Key.Value),
463},
990289beLukasz Mierzwa2 years ago464}, false
a138844fLukasz Mierzwa2 years ago465}
466}
467}
468
a68c8b60Lukasz Mierzwa5 years ago469if recordPart != nil && exprPart != nil {
eef6745bLukasz Mierzwa2 years ago470rule = Rule{
26ebaab1Lukasz Mierzwa2 years ago471Lines: lines,
eef6745bLukasz Mierzwa2 years ago472RecordingRule: &RecordingRule{
473Record: *recordPart,
474Expr: *exprPart,
475Labels: labelsPart,
476},
37a97b61Lukasz Mierzwa2 years ago477Comments: ruleComments,
eef6745bLukasz Mierzwa2 years ago478}
990289beLukasz Mierzwa2 years ago479return rule, false
a68c8b60Lukasz Mierzwa5 years ago480}
481
482if alertPart != nil && exprPart != nil {
eef6745bLukasz Mierzwa2 years ago483rule = Rule{
26ebaab1Lukasz Mierzwa2 years ago484Lines: lines,
eef6745bLukasz Mierzwa2 years ago485AlertingRule: &AlertingRule{
486Alert: *alertPart,
487Expr: *exprPart,
488For: forPart,
489KeepFiringFor: keepFiringForPart,
490Labels: labelsPart,
491Annotations: annotationsPart,
492},
37a97b61Lukasz Mierzwa2 years ago493Comments: ruleComments,
eef6745bLukasz Mierzwa2 years ago494}
990289beLukasz Mierzwa2 years ago495return rule, false
a68c8b60Lukasz Mierzwa5 years ago496}
497
990289beLukasz Mierzwa2 years ago498return rule, true
a68c8b60Lukasz Mierzwa5 years ago499}
14ab07ffLukasz Mierzwa4 years ago500
3e11b598Lukasz Mierzwa4 years ago501func unpackNodes(node *yaml.Node) []*yaml.Node {
502nodes := make([]*yaml.Node, 0, len(node.Content))
503var isMerge bool
504for _, part := range node.Content {
f9e7354aLukasz Mierzwa2 years ago505if part.ShortTag() == mergeTag && part.Value == "<<" {
3e11b598Lukasz Mierzwa4 years ago506isMerge = true
507}
508
509if part.Alias != nil {
510if isMerge {
511nodes = append(nodes, resolveMapAlias(part, node).Content...)
512} else {
513nodes = append(nodes, resolveMapAlias(part, part))
514}
515isMerge = false
516continue
517}
518if isMerge {
a7436d62Lukasz Mierzwa3 months ago519if part.Kind == yaml.SequenceNode {
520for _, seqItem := range part.Content {
521if seqItem.Alias != nil {
522nodes = append(nodes, resolveMapAlias(seqItem, node).Content...)
523}
524}
525isMerge = false
526}
3e11b598Lukasz Mierzwa4 years ago527continue
528}
529nodes = append(nodes, part)
530}
531return nodes
532}
533
3244b0e5Lukasz Mierzwa10 months ago534func hasKey(node *yaml.Node, key string) bool {
3e11b598Lukasz Mierzwa4 years ago535if node.Kind != yaml.MappingNode {
3244b0e5Lukasz Mierzwa10 months ago536return false
0952f2baLukasz Mierzwa10 months ago537}
c395d1e6Lukasz Mierzwa10 months ago538for i := 0; i < len(node.Content); i += 2 {
539if node.Content[i].Value == key {
3244b0e5Lukasz Mierzwa10 months ago540return true
3e11b598Lukasz Mierzwa4 years ago541}
542}
3244b0e5Lukasz Mierzwa10 months ago543return false
3e11b598Lukasz Mierzwa4 years ago544}
545
4b4ce464Lukasz Mierzwa3 years ago546func hasValue(node *YamlNode) bool {
dcb7e3a8Lukasz Mierzwa8 months ago547return node != nil && node.Value != ""
4b4ce464Lukasz Mierzwa3 years ago548}
549
3002aa5eLukasz Mierzwa1 years ago550func ensureRequiredKeys(lines diags.LineRange, key string, keyVal *YamlNode, expr *PromQLExpr) (Rule, bool) {
3c8a7f4bLukasz Mierzwa2 years ago551if keyVal == nil {
26ebaab1Lukasz Mierzwa2 years ago552return Rule{Lines: lines}, true
4b4ce464Lukasz Mierzwa3 years ago553}
3c8a7f4bLukasz Mierzwa2 years ago554if !hasValue(keyVal) {
4b4ce464Lukasz Mierzwa3 years ago555return Rule{
26ebaab1Lukasz Mierzwa2 years ago556Lines: lines,
4b4ce464Lukasz Mierzwa3 years ago557Error: ParseError{
a0a4e8b3Lukasz Mierzwa1 years ago558Line: keyVal.Pos.Lines().Last,
3c8a7f4bLukasz Mierzwa2 years ago559Err: fmt.Errorf("%s value cannot be empty", key),
4b4ce464Lukasz Mierzwa3 years ago560},
561}, false
562}
563if expr == nil {
564return Rule{
26ebaab1Lukasz Mierzwa2 years ago565Lines: lines,
4b4ce464Lukasz Mierzwa3 years ago566Error: ParseError{
a0a4e8b3Lukasz Mierzwa1 years ago567Line: keyVal.Pos.Lines().Last,
4b4ce464Lukasz Mierzwa3 years ago568Err: fmt.Errorf("missing %s key", exprKey),
569},
570}, false
571}
572if !hasValue(expr.Value) {
573return Rule{
26ebaab1Lukasz Mierzwa2 years ago574Lines: lines,
4b4ce464Lukasz Mierzwa3 years ago575Error: ParseError{
a0a4e8b3Lukasz Mierzwa1 years ago576Line: expr.Value.Pos.Lines().Last,
4b4ce464Lukasz Mierzwa3 years ago577Err: fmt.Errorf("%s value cannot be empty", exprKey),
578},
579}, false
580}
26ebaab1Lukasz Mierzwa2 years ago581return Rule{Lines: lines}, true
4b4ce464Lukasz Mierzwa3 years ago582}
583
3e11b598Lukasz Mierzwa4 years ago584func resolveMapAlias(part, parent *yaml.Node) *yaml.Node {
cfbb2842Lukasz Mierzwa4 years ago585node := *part
3e11b598Lukasz Mierzwa4 years ago586node.Content = nil
587var ok bool
588for i, alias := range part.Alias.Content {
589if i%2 == 0 {
590ok = !hasKey(parent, alias.Value)
591}
592if ok {
593node.Content = append(node.Content, alias)
594}
595if i%2 == 1 {
596ok = false
597}
598}
599return &node
600}
601
3002aa5eLukasz Mierzwa1 years ago602func duplicatedKeyError(lines diags.LineRange, line int, key string) (Rule, bool) {
14ab07ffLukasz Mierzwa4 years ago603rule := Rule{
26ebaab1Lukasz Mierzwa2 years ago604Lines: lines,
14ab07ffLukasz Mierzwa4 years ago605Error: ParseError{
606Line: line,
607Err: fmt.Errorf("duplicated %s key", key),
608},
609}
990289beLukasz Mierzwa2 years ago610return rule, false
1453b819Lukasz Mierzwa2 years ago611}
612
3002aa5eLukasz Mierzwa1 years ago613func invalidValueError(lines diags.LineRange, line int, key, expectedTag, gotTag string) (Rule, bool) {
1453b819Lukasz Mierzwa2 years ago614rule := Rule{
615Lines: lines,
616Error: ParseError{
617Line: line,
f9e7354aLukasz Mierzwa2 years ago618Err: fmt.Errorf("%s value must be a %s, got %s instead", key, expectedTag, gotTag),
1453b819Lukasz Mierzwa2 years ago619},
620}
990289beLukasz Mierzwa2 years ago621return rule, false
1453b819Lukasz Mierzwa2 years ago622}
623
624func isTag(tag, expected string) bool {
f9e7354aLukasz Mierzwa2 years ago625if tag == nullTag {
1453b819Lukasz Mierzwa2 years ago626return true
627}
628return tag == expected
629}
630
f9e7354aLukasz Mierzwa2 years ago631type yamlMap struct {
632key *yaml.Node
633val *yaml.Node
1453b819Lukasz Mierzwa2 years ago634}
635
f9e7354aLukasz Mierzwa2 years ago636func mappingNodes(node *yaml.Node) []yamlMap {
9cbd2775Lukasz Mierzwa9 months ago637if node.Kind != yaml.MappingNode {
638return nil
639}
3ef6d629Lukasz Mierzwa10 months ago640m := make([]yamlMap, 0, len(node.Content)/2)
ec87bdc0Lukasz Mierzwa10 months ago641for i := 0; i < len(node.Content); i += 2 {
642m = append(m, yamlMap{key: node.Content[i], val: node.Content[i+1]})
1453b819Lukasz Mierzwa2 years ago643}
644return m
14ab07ffLukasz Mierzwa4 years ago645}
f9e7354aLukasz Mierzwa2 years ago646
3002aa5eLukasz Mierzwa1 years ago647func rangeFromYamlMaps(m []yamlMap) (lr diags.LineRange) {
f9e7354aLukasz Mierzwa2 years ago648for _, entry := range m {
649if lr.First == 0 {
650lr.First = entry.key.Line
651lr.Last = entry.val.Line
652}
653lr.First = min(lr.First, entry.key.Line, entry.val.Line)
654lr.Last = max(lr.Last, entry.key.Line, entry.val.Line)
655}
656return lr
657}
658
659var (
660yamlErrRe = regexp.MustCompile("^yaml: line (.+): (.+)")
661yamlUnmarshalErrRe = regexp.MustCompile("^yaml: unmarshal errors:\n line (.+): (.+)")
662)
663
664func tryDecodingYamlError(err error) ParseError {
665for _, re := range []*regexp.Regexp{yamlErrRe, yamlUnmarshalErrRe} {
666parts := re.FindStringSubmatch(err.Error())
667if len(parts) > 2 {
668if line, err2 := strconv.Atoi(parts[1]); line > 0 && err2 == nil {
669return ParseError{
670Line: line,
671Err: errors.New(parts[2]),
672}
673}
674}
675}
676return ParseError{Line: 1, Err: err}
677}
636c2a98Lukasz Mierzwa1 years ago678
679func countLeadingSpace(line string) (i int) {
680for _, r := range line {
681if r != ' ' {
682return i
683}
684i++
685}
686return i
687}
7732eaabLukasz Mierzwa1 years ago688
689func validateStringMap(field string, nodes []yamlMap, offsetLine int, lines diags.LineRange) (bool, ParseError, diags.LineRange) {
690names := map[string]struct{}{}
691for _, entry := range nodes {
692if !isTag(entry.val.ShortTag(), strTag) {
693return false, ParseError{
694Line: entry.val.Line + offsetLine,
695Err: fmt.Errorf("%s %s value must be a %s, got %s instead", field, entry.key.Value, describeTag(strTag), describeTag(entry.val.ShortTag())),
696}, lines
697}
698if _, ok := names[entry.key.Value]; ok {
699return false, ParseError{
700Line: entry.key.Line,
701Err: fmt.Errorf("duplicated %s key %s", field, entry.key.Value),
702}, rangeFromYamlMaps(nodes)
703}
704names[entry.key.Value] = struct{}{}
705}
706return true, ParseError{}, lines
707}