Test template expansion while loading groups (#4537)

Signed-off-by: Ganesh Vernekar <cs15btech11018@iith.ac.in>
This commit is contained in:
Ganesh Vernekar 2018-09-13 18:25:58 +05:30 committed by Brian Brazil
parent 4ae3bce260
commit 05726c5ea2
4 changed files with 152 additions and 7 deletions

View file

@ -14,11 +14,16 @@
package rulefmt
import (
"context"
"fmt"
"io/ioutil"
"time"
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/pkg/timestamp"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/template"
yaml "gopkg.in/yaml.v2"
)
@ -137,6 +142,50 @@ func (r *Rule) Validate() (errs []error) {
}
}
errs = append(errs, testTemplateParsing(r)...)
return errs
}
// testTemplateParsing checks if the templates used in labels and annotations
// of the alerting rules are parsed correctly.
func testTemplateParsing(rl *Rule) (errs []error) {
if rl.Alert == "" {
// Not an alerting rule.
return errs
}
// Trying to parse templates.
tmplData := template.AlertTemplateData(make(map[string]string), 0)
defs := "{{$labels := .Labels}}{{$value := .Value}}"
parseTest := func(text string) error {
tmpl := template.NewTemplateExpander(
context.TODO(),
defs+text,
"__alert_"+rl.Alert,
tmplData,
model.Time(timestamp.FromTime(time.Now())),
nil,
nil,
)
return tmpl.ParseTest()
}
// Parsing Labels.
for _, val := range rl.Labels {
err := parseTest(val)
if err != nil {
errs = append(errs, fmt.Errorf("msg=%s", err.Error()))
}
}
// Parsing Annotations.
for _, val := range rl.Annotations {
err := parseTest(val)
if err != nil {
errs = append(errs, fmt.Errorf("msg=%s", err.Error()))
}
}
return errs
}

View file

@ -17,6 +17,8 @@ import (
"path/filepath"
"strings"
"testing"
"github.com/prometheus/prometheus/util/testutil"
)
func TestParseFileSuccess(t *testing.T) {
@ -79,3 +81,83 @@ func TestParseFileFailure(t *testing.T) {
}
}
func TestTemplateParsing(t *testing.T) {
tests := []struct {
ruleString string
shouldPass bool
}{
{
ruleString: `
groups:
- name: example
rules:
- alert: InstanceDown
expr: up == 0
for: 5m
labels:
severity: "page"
annotations:
summary: "Instance {{ $labels.instance }} down"
`,
shouldPass: true,
},
{
// `$label` instead of `$labels`.
ruleString: `
groups:
- name: example
rules:
- alert: InstanceDown
expr: up == 0
for: 5m
labels:
severity: "page"
annotations:
summary: "Instance {{ $label.instance }} down"
`,
shouldPass: false,
},
{
// `$this_is_wrong`.
ruleString: `
groups:
- name: example
rules:
- alert: InstanceDown
expr: up == 0
for: 5m
labels:
severity: "{{$this_is_wrong}}"
annotations:
summary: "Instance {{ $labels.instance }} down"
`,
shouldPass: false,
},
{
// `$labels.quantile * 100`.
ruleString: `
groups:
- name: example
rules:
- alert: InstanceDown
expr: up == 0
for: 5m
labels:
severity: "page"
annotations:
summary: "Instance {{ $labels.instance }} down"
description: "{{$labels.quantile * 100}}"
`,
shouldPass: false,
},
}
for _, tst := range tests {
rgs, errs := Parse([]byte(tst.ruleString))
testutil.Assert(t, rgs != nil, "Rule parsing, rule=\n"+tst.ruleString)
passed := (tst.shouldPass && len(errs) == 0) || (!tst.shouldPass && len(errs) > 0)
testutil.Assert(t, passed, "Rule validation failed, rule=\n"+tst.ruleString)
}
}

View file

@ -292,13 +292,7 @@ func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, query QueryFunc,
l[lbl.Name] = lbl.Value
}
tmplData := struct {
Labels map[string]string
Value float64
}{
Labels: l,
Value: smpl.V,
}
tmplData := template.AlertTemplateData(l, smpl.V)
// Inject some convenience variables that are easier to remember for users
// who are not used to Go's templating system.
defs := "{{$labels := .Labels}}{{$value := .Value}}"

View file

@ -243,6 +243,17 @@ func NewTemplateExpander(
}
}
// AlertTemplateData returns the interface to be used in expanding the template.
func AlertTemplateData(labels map[string]string, value float64) interface{} {
return struct {
Labels map[string]string
Value float64
}{
Labels: labels,
Value: value,
}
}
// Funcs adds the functions in fm to the Expander's function map.
// Existing functions will be overwritten in case of conflict.
func (te Expander) Funcs(fm text_template.FuncMap) {
@ -315,3 +326,12 @@ func (te Expander) ExpandHTML(templateFiles []string) (result string, resultErr
}
return buffer.String(), nil
}
// ParseTest parses the templates and returns the error if any.
func (te Expander) ParseTest() error {
_, err := text_template.New(te.name).Funcs(te.funcMap).Option("missingkey=zero").Parse(te.text)
if err != nil {
return err
}
return nil
}