alertmanager/template/template.go

221 lines
5.3 KiB
Go

// Copyright 2015 Prometheus Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package template
import (
"bytes"
"sort"
"strings"
tmplhtml "html/template"
tmpltext "text/template"
"github.com/prometheus/common/model"
"github.com/prometheus/alertmanager/types"
)
// Template bundles a text and a html template instance.
type Template struct {
text *tmpltext.Template
html *tmplhtml.Template
}
// FromGlobs calls ParseGlob on all path globs provided and returns the
// resulting Template.
func FromGlobs(paths ...string) (*Template, error) {
t := &Template{
text: tmpltext.New("").Option("missingkey=zero"),
html: tmplhtml.New("").Option("missingkey=zero"),
}
var err error
t.text = t.text.Funcs(tmpltext.FuncMap(DefaultFuncs))
t.html = t.html.Funcs(tmplhtml.FuncMap(DefaultFuncs))
for _, tp := range paths {
if t.text, err = t.text.ParseGlob(tp); err != nil {
return nil, err
}
if t.html, err = t.html.ParseGlob(tp); err != nil {
return nil, err
}
}
return t, nil
}
// ExecuteTextString needs a meaningful doc comment (TODO(fabxc)).
func (t *Template) ExecuteTextString(text string, data interface{}) (string, error) {
tmpl, err := t.text.Clone()
if err != nil {
return "", err
}
tmpl, err = tmpl.New("").Parse(text)
if err != nil {
return "", err
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, data)
return buf.String(), err
}
// ExecuteHTMLString needs a meaningful doc comment (TODO(fabxc)).
func (t *Template) ExecuteHTMLString(html string, data interface{}) (string, error) {
tmpl, err := t.html.Clone()
if err != nil {
return "", err
}
tmpl, err = tmpl.New("").Parse(html)
if err != nil {
return "", err
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, data)
return buf.String(), err
}
type FuncMap map[string]interface{}
var DefaultFuncs = FuncMap{
"toUpper": strings.ToUpper,
"toLower": strings.ToLower,
"toTitle": strings.ToTitle,
// sortedPairs allows for in-order iteration of key/value pairs.
"sortedPairs": func(m map[string]string) []Pair {
var (
pairs = make([]Pair, 0, len(m))
keys = make([]string, 0, len(m))
sortStart = 0
)
for k := range m {
if k == string(model.AlertNameLabel) {
keys = append([]string{k}, keys...)
sortStart = 1
} else {
keys = append(keys, k)
}
}
sort.Strings(keys[sortStart:])
for _, k := range keys {
pairs = append(pairs, Pair{k, m[k]})
}
return pairs
},
"firing": func(alerts []Alert) []Alert {
res := []Alert{}
for _, a := range alerts {
if a.Status == string(model.AlertFiring) {
res = append(res, a)
}
}
return res
},
"resolved": func(alerts []Alert) []Alert {
res := []Alert{}
for _, a := range alerts {
if a.Status == string(model.AlertResolved) {
res = append(res, a)
}
}
return res
},
}
// Pair is a key/value string pair.
type Pair struct {
Name, Value string
}
// Data is the data passed to notification templates.
// End-users should not be exposed to Go's type system,
// as this will confuse them and prevent simple things like
// simple equality checks to fail. Map everything to float64/string.
type Data struct {
Status string
Alerts []Alert
GroupLabels map[string]string
CommonLabels map[string]string
CommonAnnotations map[string]string
ExternalURL string
}
// Alert holds one alert for notification templates.
type Alert struct {
Status string
Labels map[string]string
Annotations map[string]string
}
func NewData(groupLabels model.LabelSet, as ...*types.Alert) *Data {
alerts := types.Alerts(as...)
data := &Data{
Status: string(alerts.Status()),
Alerts: make([]Alert, 0, len(alerts)),
GroupLabels: map[string]string{},
CommonLabels: map[string]string{},
CommonAnnotations: map[string]string{},
ExternalURL: "something",
}
for _, a := range alerts {
alert := Alert{
Status: string(a.Status()),
Labels: make(map[string]string, len(a.Labels)),
Annotations: make(map[string]string, len(a.Annotations)),
}
for k, v := range a.Labels {
alert.Labels[string(k)] = string(v)
}
for k, v := range a.Annotations {
alert.Annotations[string(k)] = string(v)
}
data.Alerts = append(data.Alerts, alert)
}
for k, v := range groupLabels {
data.GroupLabels[string(k)] = string(v)
}
if len(alerts) >= 1 {
var (
commonLabels = alerts[0].Labels.Clone()
commonAnnotations = alerts[0].Annotations.Clone()
)
for _, a := range alerts[1:] {
for ln, lv := range commonLabels {
if a.Labels[ln] != lv {
delete(commonLabels, ln)
}
}
for an, av := range commonAnnotations {
if a.Annotations[an] != av {
delete(commonAnnotations, an)
}
}
}
for k, v := range commonLabels {
data.CommonLabels[string(k)] = string(v)
}
for k, v := range commonAnnotations {
data.CommonAnnotations[string(k)] = string(v)
}
}
return data
}