2020-04-06 13:44:03 +02:00
|
|
|
package notifier
|
2020-02-16 19:59:02 +01:00
|
|
|
|
2020-03-13 11:19:31 +01:00
|
|
|
import (
|
2020-03-27 17:31:16 +01:00
|
|
|
"bytes"
|
2020-03-29 00:48:30 +01:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2020-03-27 17:31:16 +01:00
|
|
|
"strings"
|
|
|
|
"text/template"
|
2020-03-13 11:19:31 +01:00
|
|
|
"time"
|
|
|
|
)
|
2020-02-16 19:59:02 +01:00
|
|
|
|
2020-03-13 11:19:31 +01:00
|
|
|
// Alert the triggered alert
|
2020-04-06 13:44:03 +02:00
|
|
|
// TODO: Looks like alert name isn't unique
|
2020-03-13 11:19:31 +01:00
|
|
|
type Alert struct {
|
|
|
|
Group string
|
|
|
|
Name string
|
2020-04-06 13:44:03 +02:00
|
|
|
Labels map[string]string
|
2020-03-13 11:19:31 +01:00
|
|
|
Annotations map[string]string
|
2020-04-06 13:44:03 +02:00
|
|
|
State AlertState
|
2020-03-13 11:19:31 +01:00
|
|
|
|
|
|
|
Start time.Time
|
|
|
|
End time.Time
|
|
|
|
Value float64
|
|
|
|
}
|
|
|
|
|
2020-04-06 13:44:03 +02:00
|
|
|
// 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
|
|
|
|
)
|
|
|
|
|
2020-03-27 17:31:16 +01:00
|
|
|
type alertTplData struct {
|
2020-04-06 13:44:03 +02:00
|
|
|
Labels map[string]string
|
|
|
|
Value float64
|
2020-03-27 17:31:16 +01:00
|
|
|
}
|
|
|
|
|
2020-04-06 13:44:03 +02:00
|
|
|
const tplHeader = `{{ $value := .Value }}{{ $labels := .Labels }}`
|
2020-03-27 17:31:16 +01:00
|
|
|
|
2020-04-06 13:44:03 +02:00
|
|
|
// ExecTemplate executes the Alert template for give
|
|
|
|
// map of annotations.
|
|
|
|
func (a *Alert) ExecTemplate(annotations map[string]string) (map[string]string, error) {
|
|
|
|
tplData := alertTplData{Value: a.Value, Labels: a.Labels}
|
|
|
|
return templateAnnotations(annotations, tplHeader, tplData)
|
2020-03-13 11:19:31 +01:00
|
|
|
}
|
|
|
|
|
2020-04-06 13:44:03 +02:00
|
|
|
// ValidateAnnotations validate annotations for possible template error, uses empty data for template population
|
|
|
|
func ValidateAnnotations(annotations map[string]string) error {
|
|
|
|
_, err := templateAnnotations(annotations, tplHeader, alertTplData{
|
|
|
|
Labels: map[string]string{},
|
|
|
|
Value: 0,
|
|
|
|
})
|
|
|
|
return err
|
2020-03-27 17:31:16 +01:00
|
|
|
}
|
|
|
|
|
2020-03-29 00:48:30 +01:00
|
|
|
func templateAnnotations(annotations map[string]string, header string, data alertTplData) (map[string]string, error) {
|
2020-03-27 17:31:16 +01:00
|
|
|
var builder strings.Builder
|
|
|
|
var buf bytes.Buffer
|
2020-03-29 00:48:30 +01:00
|
|
|
eg := errGroup{}
|
2020-03-27 17:31:16 +01:00
|
|
|
r := make(map[string]string, len(annotations))
|
|
|
|
for key, text := range annotations {
|
|
|
|
r[key] = text
|
|
|
|
buf.Reset()
|
|
|
|
builder.Reset()
|
|
|
|
builder.Grow(len(header) + len(text))
|
|
|
|
builder.WriteString(header)
|
|
|
|
builder.WriteString(text)
|
2020-03-29 00:48:30 +01:00
|
|
|
if err := templateAnnotation(&buf, builder.String(), data); err != nil {
|
|
|
|
eg.errs = append(eg.errs, fmt.Sprintf("key %s, template %s:%s", key, text, err))
|
2020-03-13 11:19:31 +01:00
|
|
|
continue
|
|
|
|
}
|
2020-03-27 17:31:16 +01:00
|
|
|
r[key] = buf.String()
|
2020-03-13 11:19:31 +01:00
|
|
|
}
|
2020-03-29 00:48:30 +01:00
|
|
|
return r, eg.err()
|
|
|
|
}
|
|
|
|
|
|
|
|
func templateAnnotation(dst io.Writer, text string, data alertTplData) error {
|
2020-04-01 17:17:53 +02:00
|
|
|
tpl, err := template.New("").Funcs(tmplFunc).Option("missingkey=zero").Parse(text)
|
2020-03-29 00:48:30 +01:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error parsing annotation:%w", err)
|
|
|
|
}
|
|
|
|
if err = tpl.Execute(dst, data); err != nil {
|
|
|
|
return fmt.Errorf("error evaluating annotation template:%w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type errGroup struct {
|
|
|
|
errs []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (eg *errGroup) err() error {
|
|
|
|
if eg == nil || len(eg.errs) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return eg
|
|
|
|
}
|
|
|
|
|
|
|
|
func (eg *errGroup) Error() string {
|
|
|
|
return fmt.Sprintf("errors:%s", strings.Join(eg.errs, "\n"))
|
2020-02-16 19:59:02 +01:00
|
|
|
}
|