Add externalURL template function (#2716)

This allows users to e.g. add links back to the generating Prometheus
right in their alert templates.
This commit is contained in:
Julius Volz 2017-05-13 15:47:04 +02:00 committed by GitHub
parent 0e0da989c5
commit ac203ef0ee
8 changed files with 34 additions and 12 deletions

View file

@ -15,6 +15,7 @@ package rules
import ( import (
"fmt" "fmt"
"net/url"
"sync" "sync"
"time" "time"
@ -148,7 +149,7 @@ const resolvedRetention = 15 * time.Minute
// Eval evaluates the rule expression and then creates pending alerts and fires // Eval evaluates the rule expression and then creates pending alerts and fires
// or removes previously pending alerts accordingly. // or removes previously pending alerts accordingly.
func (r *AlertingRule) Eval(ctx context.Context, ts model.Time, engine *promql.Engine, externalURLPath string) (model.Vector, error) { func (r *AlertingRule) Eval(ctx context.Context, ts model.Time, engine *promql.Engine, externalURL *url.URL) (model.Vector, error) {
query, err := engine.NewInstantQuery(r.vector.String(), ts) query, err := engine.NewInstantQuery(r.vector.String(), ts)
if err != nil { if err != nil {
return nil, err return nil, err
@ -191,7 +192,7 @@ func (r *AlertingRule) Eval(ctx context.Context, ts model.Time, engine *promql.E
tmplData, tmplData,
ts, ts,
engine, engine,
externalURLPath, externalURL,
) )
result, err := tmpl.Expand() result, err := tmpl.Expand()
if err != nil { if err != nil {

View file

@ -113,7 +113,7 @@ const (
type Rule interface { type Rule interface {
Name() string Name() string
// eval evaluates the rule, including any associated recording or alerting actions. // eval evaluates the rule, including any associated recording or alerting actions.
Eval(context.Context, model.Time, *promql.Engine, string) (model.Vector, error) Eval(context.Context, model.Time, *promql.Engine, *url.URL) (model.Vector, error)
// String returns a human-readable string representation of the rule. // String returns a human-readable string representation of the rule.
String() string String() string
// HTMLSnippet returns a human-readable string representation of the rule, // HTMLSnippet returns a human-readable string representation of the rule,
@ -274,7 +274,7 @@ func (g *Group) Eval() {
evalTotal.WithLabelValues(rtyp).Inc() evalTotal.WithLabelValues(rtyp).Inc()
vector, err := rule.Eval(g.opts.Context, now, g.opts.QueryEngine, g.opts.ExternalURL.Path) vector, err := rule.Eval(g.opts.Context, now, g.opts.QueryEngine, g.opts.ExternalURL)
if err != nil { if err != nil {
// Canceled queries are intentional termination of queries. This normally // Canceled queries are intentional termination of queries. This normally
// happens on shutdown and thus we skip logging of any errors here. // happens on shutdown and thus we skip logging of any errors here.

View file

@ -105,7 +105,7 @@ func TestAlertingRule(t *testing.T) {
for i, test := range tests { for i, test := range tests {
evalTime := model.Time(0).Add(test.time) evalTime := model.Time(0).Add(test.time)
res, err := rule.Eval(suite.Context(), evalTime, suite.QueryEngine(), "") res, err := rule.Eval(suite.Context(), evalTime, suite.QueryEngine(), nil)
if err != nil { if err != nil {
t.Fatalf("Error during alerting rule evaluation: %s", err) t.Fatalf("Error during alerting rule evaluation: %s", err)
} }

View file

@ -16,6 +16,7 @@ package rules
import ( import (
"fmt" "fmt"
"html/template" "html/template"
"net/url"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -46,7 +47,7 @@ func (rule RecordingRule) Name() string {
} }
// Eval evaluates the rule and then overrides the metric names and labels accordingly. // Eval evaluates the rule and then overrides the metric names and labels accordingly.
func (rule RecordingRule) Eval(ctx context.Context, timestamp model.Time, engine *promql.Engine, _ string) (model.Vector, error) { func (rule RecordingRule) Eval(ctx context.Context, timestamp model.Time, engine *promql.Engine, _ *url.URL) (model.Vector, error) {
query, err := engine.NewInstantQuery(rule.vector.String(), timestamp) query, err := engine.NewInstantQuery(rule.vector.String(), timestamp)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -63,7 +63,7 @@ func TestRuleEval(t *testing.T) {
for _, test := range suite { for _, test := range suite {
rule := NewRecordingRule(test.name, test.expr, test.labels) rule := NewRecordingRule(test.name, test.expr, test.labels)
result, err := rule.Eval(ctx, now, engine, "") result, err := rule.Eval(ctx, now, engine, nil)
if err != nil { if err != nil {
t.Fatalf("Error evaluating %s", test.name) t.Fatalf("Error evaluating %s", test.name)
} }

View file

@ -18,6 +18,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"net/url"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
@ -111,7 +112,7 @@ type Expander struct {
} }
// NewTemplateExpander returns a template expander ready to use. // NewTemplateExpander returns a template expander ready to use.
func NewTemplateExpander(ctx context.Context, text string, name string, data interface{}, timestamp model.Time, queryEngine *promql.Engine, pathPrefix string) *Expander { func NewTemplateExpander(ctx context.Context, text string, name string, data interface{}, timestamp model.Time, queryEngine *promql.Engine, externalURL *url.URL) *Expander {
return &Expander{ return &Expander{
text: text, text: text,
name: name, name: name,
@ -247,7 +248,10 @@ func NewTemplateExpander(ctx context.Context, text string, name string, data int
return fmt.Sprint(t) return fmt.Sprint(t)
}, },
"pathPrefix": func() string { "pathPrefix": func() string {
return pathPrefix return externalURL.Path
},
"externalURL": func() string {
return externalURL.String()
}, },
}, },
} }

View file

@ -15,6 +15,7 @@ package template
import ( import (
"math" "math"
"net/url"
"testing" "testing"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -196,6 +197,16 @@ func TestTemplateExpansion(t *testing.T) {
output: "x", output: "x",
html: true, html: true,
}, },
{
// pathPrefix.
text: "{{ pathPrefix }}",
output: "/path/prefix",
},
{
// externalURL.
text: "{{ externalURL }}",
output: "http://testhost:9090/path/prefix",
},
} }
time := model.Time(0) time := model.Time(0)
@ -218,10 +229,15 @@ func TestTemplateExpansion(t *testing.T) {
engine := promql.NewEngine(storage, nil) engine := promql.NewEngine(storage, nil)
extURL, err := url.Parse("http://testhost:9090/path/prefix")
if err != nil {
panic(err)
}
for i, s := range scenarios { for i, s := range scenarios {
var result string var result string
var err error var err error
expander := NewTemplateExpander(context.Background(), s.text, "test", s.input, time, engine, "") expander := NewTemplateExpander(context.Background(), s.text, "test", s.input, time, engine, extURL)
if s.html { if s.html {
result, err = expander.ExpandHTML(nil) result, err = expander.ExpandHTML(nil)
} else { } else {

View file

@ -326,7 +326,7 @@ func (h *Handler) consoles(w http.ResponseWriter, r *http.Request) {
Path: strings.TrimLeft(name, "/"), Path: strings.TrimLeft(name, "/"),
} }
tmpl := template.NewTemplateExpander(h.context, string(text), "__console_"+name, data, h.now(), h.queryEngine, h.options.ExternalURL.Path) tmpl := template.NewTemplateExpander(h.context, string(text), "__console_"+name, data, h.now(), h.queryEngine, h.options.ExternalURL)
filenames, err := filepath.Glob(h.options.ConsoleLibrariesPath + "/*.lib") filenames, err := filepath.Glob(h.options.ConsoleLibrariesPath + "/*.lib")
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -521,7 +521,7 @@ func (h *Handler) executeTemplate(w http.ResponseWriter, name string, data inter
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
tmpl := template.NewTemplateExpander(h.context, text, name, data, h.now(), h.queryEngine, h.options.ExternalURL.Path) tmpl := template.NewTemplateExpander(h.context, text, name, data, h.now(), h.queryEngine, h.options.ExternalURL)
tmpl.Funcs(tmplFuncs(h.consolesPath(), h.options)) tmpl.Funcs(tmplFuncs(h.consolesPath(), h.options))
result, err := tmpl.ExpandHTML(nil) result, err := tmpl.ExpandHTML(nil)