mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-22 16:36:27 +01:00
3616337812
Function `ValidateTemplates`, used on the vmalert startup, is supposed to check whether used templates and functions in loaded rules are correct. The function was parsing and executing loaded templates. However, rules may contain functions which can't be executed without values (label values or query results), like `slice`. Because of this, validation for completely valid expression `{{ slice $labels.job 9 }}` will fail since `$labels.job` is empty during validation. This PR updates `ValidateTemplates` function to only parse templates without executing them. https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2514 Signed-off-by: hagen1778 <roman@victoriametrics.com>
170 lines
5.0 KiB
Go
170 lines
5.0 KiB
Go
package notifier
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
|
|
)
|
|
|
|
// Alert the triggered alert
|
|
// TODO: Looks like alert name isn't unique
|
|
type Alert struct {
|
|
// GroupID contains the ID of the parent rules group
|
|
GroupID uint64
|
|
// Name represents Alert name
|
|
Name string
|
|
// Labels is the list of label-value pairs attached to the Alert
|
|
Labels map[string]string
|
|
// Annotations is the list of annotations generated on Alert evaluation
|
|
Annotations map[string]string
|
|
// State represents the current state of the Alert
|
|
State AlertState
|
|
// Expr contains expression that was executed to generate the Alert
|
|
Expr string
|
|
// ActiveAt defines the moment of time when Alert has become active
|
|
ActiveAt time.Time
|
|
// Start defines the moment of time when Alert has become firing
|
|
Start time.Time
|
|
// End defines the moment of time when Alert supposed to expire
|
|
End time.Time
|
|
// ResolvedAt defines the moment when Alert was switched from Firing to Inactive
|
|
ResolvedAt time.Time
|
|
// LastSent defines the moment when Alert was sent last time
|
|
LastSent time.Time
|
|
// Value stores the value returned from evaluating expression from Expr field
|
|
Value float64
|
|
// ID is the unique identifer for the Alert
|
|
ID uint64
|
|
// Restored is true if Alert was restored after restart
|
|
Restored bool
|
|
}
|
|
|
|
// AlertState type indicates the Alert state
|
|
type AlertState int
|
|
|
|
const (
|
|
// StateInactive is the state of an alert that is neither firing nor pending.
|
|
StateInactive AlertState = iota
|
|
// StatePending is the state of an alert that has been active for less than
|
|
// the configured threshold duration.
|
|
StatePending
|
|
// StateFiring is the state of an alert that has been active for longer than
|
|
// the configured threshold duration.
|
|
StateFiring
|
|
)
|
|
|
|
// String stringer for AlertState
|
|
func (as AlertState) String() string {
|
|
switch as {
|
|
case StateFiring:
|
|
return "firing"
|
|
case StatePending:
|
|
return "pending"
|
|
}
|
|
return "inactive"
|
|
}
|
|
|
|
// AlertTplData is used to execute templating
|
|
type AlertTplData struct {
|
|
Labels map[string]string
|
|
Value float64
|
|
Expr string
|
|
}
|
|
|
|
var tplHeaders = []string{
|
|
"{{ $value := .Value }}",
|
|
"{{ $labels := .Labels }}",
|
|
"{{ $expr := .Expr }}",
|
|
"{{ $externalLabels := .ExternalLabels }}",
|
|
"{{ $externalURL := .ExternalURL }}",
|
|
}
|
|
|
|
// ExecTemplate executes the Alert template for given
|
|
// map of annotations.
|
|
// Every alert could have a different datasource, so function
|
|
// requires a queryFunction as an argument.
|
|
func (a *Alert) ExecTemplate(q QueryFn, labels, annotations map[string]string) (map[string]string, error) {
|
|
tplData := AlertTplData{Value: a.Value, Labels: labels, Expr: a.Expr}
|
|
return templateAnnotations(annotations, tplData, funcsWithQuery(q), true)
|
|
}
|
|
|
|
// ExecTemplate executes the given template for given annotations map.
|
|
func ExecTemplate(q QueryFn, annotations map[string]string, tpl AlertTplData) (map[string]string, error) {
|
|
return templateAnnotations(annotations, tpl, funcsWithQuery(q), true)
|
|
}
|
|
|
|
// ValidateTemplates validate annotations for possible template error, uses empty data for template population
|
|
func ValidateTemplates(annotations map[string]string) error {
|
|
_, err := templateAnnotations(annotations, AlertTplData{
|
|
Labels: map[string]string{},
|
|
Value: 0,
|
|
}, tmplFunc, false)
|
|
return err
|
|
}
|
|
|
|
func templateAnnotations(annotations map[string]string, data AlertTplData, funcs template.FuncMap, execute bool) (map[string]string, error) {
|
|
var builder strings.Builder
|
|
var buf bytes.Buffer
|
|
eg := new(utils.ErrGroup)
|
|
r := make(map[string]string, len(annotations))
|
|
tData := tplData{data, externalLabels, externalURL}
|
|
header := strings.Join(tplHeaders, "")
|
|
for key, text := range annotations {
|
|
buf.Reset()
|
|
builder.Reset()
|
|
builder.Grow(len(header) + len(text))
|
|
builder.WriteString(header)
|
|
builder.WriteString(text)
|
|
if err := templateAnnotation(&buf, builder.String(), tData, funcs, execute); err != nil {
|
|
r[key] = text
|
|
eg.Add(fmt.Errorf("key %q, template %q: %w", key, text, err))
|
|
continue
|
|
}
|
|
r[key] = buf.String()
|
|
}
|
|
return r, eg.Err()
|
|
}
|
|
|
|
type tplData struct {
|
|
AlertTplData
|
|
ExternalLabels map[string]string
|
|
ExternalURL string
|
|
}
|
|
|
|
func templateAnnotation(dst io.Writer, text string, data tplData, funcs template.FuncMap, execute bool) error {
|
|
t := template.New("").Funcs(funcs).Option("missingkey=zero")
|
|
tpl, err := t.Parse(text)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing annotation: %w", err)
|
|
}
|
|
if !execute {
|
|
return nil
|
|
}
|
|
if err = tpl.Execute(dst, data); err != nil {
|
|
return fmt.Errorf("error evaluating annotation template: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a Alert) toPromLabels(relabelCfg *promrelabel.ParsedConfigs) []prompbmarshal.Label {
|
|
var labels []prompbmarshal.Label
|
|
for k, v := range a.Labels {
|
|
labels = append(labels, prompbmarshal.Label{
|
|
Name: k,
|
|
Value: v,
|
|
})
|
|
}
|
|
promrelabel.SortLabels(labels)
|
|
if relabelCfg != nil {
|
|
return relabelCfg.Apply(labels, 0, false)
|
|
}
|
|
return labels
|
|
}
|