diff --git a/promql/lex.go b/promql/lex.go index 2ebb9cc8a..7817d3421 100644 --- a/promql/lex.go +++ b/promql/lex.go @@ -162,6 +162,11 @@ const ( itemGroupLeft itemGroupRight itemBool + // Old alerting syntax + itemWith + itemSummary + itemRunbook + itemDescription keywordsEnd ) @@ -193,6 +198,12 @@ var key = map[string]itemType{ "group_left": itemGroupLeft, "group_right": itemGroupRight, "bool": itemBool, + + // Old alerting syntax. + "with": itemWith, + "summary": itemSummary, + "runbook": itemRunbook, + "description": itemDescription, } // These are the default string representations for common items. It does not diff --git a/promql/lex_test.go b/promql/lex_test.go index 793621a3d..e3f0b4c4e 100644 --- a/promql/lex_test.go +++ b/promql/lex_test.go @@ -243,6 +243,18 @@ var tests = []struct { }, { input: "annotations", expected: []item{{itemAnnotations, 0, "annotations"}}, + }, { + input: "description", + expected: []item{{itemDescription, 0, "description"}}, + }, { + input: "summary", + expected: []item{{itemSummary, 0, "summary"}}, + }, { + input: "runbook", + expected: []item{{itemRunbook, 0, "runbook"}}, + }, { + input: "with", + expected: []item{{itemWith, 0, "with"}}, }, { input: "offset", expected: []item{{itemOffset, 0, "offset"}}, diff --git a/promql/parse.go b/promql/parse.go index 7e261a225..55a1b10b2 100644 --- a/promql/parse.go +++ b/promql/parse.go @@ -371,9 +371,10 @@ func (p *parser) alertStmt() *AlertStmt { expr := p.expr() // Optional for clause. - var duration time.Duration - var err error - + var ( + duration time.Duration + err error + ) if p.peek().typ == itemFor { p.next() dur := p.expect(itemDuration, ctx) @@ -383,23 +384,64 @@ func (p *parser) alertStmt() *AlertStmt { } } - lset := model.LabelSet{} - if p.peek().typ == itemLabels { + // Accepting WITH instead of LABELS is temporary compatibility + // with the old alerting syntax. + var ( + hasLabels bool + oldSyntax bool + labels = model.LabelSet{} + annotations = model.LabelSet{} + ) + if t := p.peek().typ; t == itemLabels { p.expect(itemLabels, ctx) - lset = p.labelSet() + labels = p.labelSet() + hasLabels = true + } else if t == itemWith { + p.expect(itemWith, ctx) + labels = p.labelSet() + oldSyntax = true + } + + // Only allow old annotation syntax if new label syntax isn't used. + if !hasLabels { + Loop: + for { + switch p.next().typ { + case itemSummary: + annotations["summary"] = model.LabelValue(p.unquoteString(p.expect(itemString, ctx).val)) + + case itemDescription: + annotations["description"] = model.LabelValue(p.unquoteString(p.expect(itemString, ctx).val)) + + case itemRunbook: + annotations["runbook"] = model.LabelValue(p.unquoteString(p.expect(itemString, ctx).val)) + + default: + p.backup() + break Loop + } + } + if len(annotations) > 0 { + oldSyntax = true + } } - annotations := model.LabelSet{} - if p.peek().typ == itemAnnotations { - p.expect(itemAnnotations, ctx) - annotations = p.labelSet() + // Only allow new annotation syntax if WITH or old annotation + // syntax weren't used. + if !oldSyntax { + if p.peek().typ == itemAnnotations { + p.expect(itemAnnotations, ctx) + annotations = p.labelSet() + } + } else { + log.Warnf("Alerting rule with old syntax found. Support for this syntax will be removed with 0.18. Please update to the new syntax.") } return &AlertStmt{ Name: name.val, Expr: expr, Duration: duration, - Labels: lset, + Labels: labels, Annotations: annotations, } } diff --git a/promql/parse_test.go b/promql/parse_test.go index 4a544cd5e..52a3381e1 100644 --- a/promql/parse_test.go +++ b/promql/parse_test.go @@ -1256,10 +1256,37 @@ var testStatement = []struct { }, }, }, { - input: `ALERT SomeName IF some_metric > 1 + input: `ALERT SomeName IF some_metric > 1 + WITH {} + SUMMARY "Global request rate low" + DESCRIPTION "The global request rate is low" + `, + expected: Statements{ + &AlertStmt{ + Name: "SomeName", + Expr: &BinaryExpr{ + Op: itemGTR, + LHS: &VectorSelector{ + Name: "some_metric", + LabelMatchers: metric.LabelMatchers{ + {Type: metric.Equal, Name: model.MetricNameLabel, Value: "some_metric"}, + }, + }, + RHS: &NumberLiteral{1}, + }, + Labels: model.LabelSet{}, + Annotations: model.LabelSet{ + "summary": "Global request rate low", + "description": "The global request rate is low", + }, + }, + }, + }, { + input: `ALERT SomeName IF some_metric > 1 + LABELS {} ANNOTATIONS { - summary = "Global request rate low", - description = "The global request rate is low" + summary = "Global request rate low", + description = "The global request rate is low", } `, expected: Statements{ @@ -1333,12 +1360,6 @@ var testStatement = []struct { }, { input: `foo{a!~"b"} = bar`, fail: true, - }, { - input: `ALERT SomeName IF time() LABELS {} - SUMMARY "Global request rate low" - DESCRIPTION "The global request rate is low" - `, - fail: true, }, // Fuzzing regression tests. {