From ac203ef0ee2e6bebec6bef2dd7057036b62adedf Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Sat, 13 May 2017 15:47:04 +0200 Subject: [PATCH] Add externalURL template function (#2716) This allows users to e.g. add links back to the generating Prometheus right in their alert templates. --- rules/alerting.go | 5 +++-- rules/manager.go | 4 ++-- rules/manager_test.go | 2 +- rules/recording.go | 3 ++- rules/recording_test.go | 2 +- template/template.go | 8 ++++++-- template/template_test.go | 18 +++++++++++++++++- web/web.go | 4 ++-- 8 files changed, 34 insertions(+), 12 deletions(-) diff --git a/rules/alerting.go b/rules/alerting.go index 514b0a7dd..6a489cfa8 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -15,6 +15,7 @@ package rules import ( "fmt" + "net/url" "sync" "time" @@ -148,7 +149,7 @@ const resolvedRetention = 15 * time.Minute // Eval evaluates the rule expression and then creates pending alerts and fires // 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) if err != nil { return nil, err @@ -191,7 +192,7 @@ func (r *AlertingRule) Eval(ctx context.Context, ts model.Time, engine *promql.E tmplData, ts, engine, - externalURLPath, + externalURL, ) result, err := tmpl.Expand() if err != nil { diff --git a/rules/manager.go b/rules/manager.go index 09d19a5da..8b017c55a 100644 --- a/rules/manager.go +++ b/rules/manager.go @@ -113,7 +113,7 @@ const ( type Rule interface { Name() string // 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() string // HTMLSnippet returns a human-readable string representation of the rule, @@ -274,7 +274,7 @@ func (g *Group) Eval() { 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 { // Canceled queries are intentional termination of queries. This normally // happens on shutdown and thus we skip logging of any errors here. diff --git a/rules/manager_test.go b/rules/manager_test.go index d229f8b96..e8e26fad4 100644 --- a/rules/manager_test.go +++ b/rules/manager_test.go @@ -105,7 +105,7 @@ func TestAlertingRule(t *testing.T) { for i, test := range tests { 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 { t.Fatalf("Error during alerting rule evaluation: %s", err) } diff --git a/rules/recording.go b/rules/recording.go index a4b7a4b8d..70fd75582 100644 --- a/rules/recording.go +++ b/rules/recording.go @@ -16,6 +16,7 @@ package rules import ( "fmt" "html/template" + "net/url" "github.com/prometheus/common/model" "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. -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) if err != nil { return nil, err diff --git a/rules/recording_test.go b/rules/recording_test.go index 00b9308c1..472eeef4d 100644 --- a/rules/recording_test.go +++ b/rules/recording_test.go @@ -63,7 +63,7 @@ func TestRuleEval(t *testing.T) { for _, test := range suite { 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 { t.Fatalf("Error evaluating %s", test.name) } diff --git a/template/template.go b/template/template.go index f468b9b6e..67e2ba073 100644 --- a/template/template.go +++ b/template/template.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "math" + "net/url" "regexp" "sort" "strings" @@ -111,7 +112,7 @@ type Expander struct { } // 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{ text: text, name: name, @@ -247,7 +248,10 @@ func NewTemplateExpander(ctx context.Context, text string, name string, data int return fmt.Sprint(t) }, "pathPrefix": func() string { - return pathPrefix + return externalURL.Path + }, + "externalURL": func() string { + return externalURL.String() }, }, } diff --git a/template/template_test.go b/template/template_test.go index 3f919ab1b..4e2031d32 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -15,6 +15,7 @@ package template import ( "math" + "net/url" "testing" "github.com/prometheus/common/model" @@ -196,6 +197,16 @@ func TestTemplateExpansion(t *testing.T) { output: "x", html: true, }, + { + // pathPrefix. + text: "{{ pathPrefix }}", + output: "/path/prefix", + }, + { + // externalURL. + text: "{{ externalURL }}", + output: "http://testhost:9090/path/prefix", + }, } time := model.Time(0) @@ -218,10 +229,15 @@ func TestTemplateExpansion(t *testing.T) { engine := promql.NewEngine(storage, nil) + extURL, err := url.Parse("http://testhost:9090/path/prefix") + if err != nil { + panic(err) + } + for i, s := range scenarios { var result string 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 { result, err = expander.ExpandHTML(nil) } else { diff --git a/web/web.go b/web/web.go index 425f56bae..7cac4effc 100644 --- a/web/web.go +++ b/web/web.go @@ -326,7 +326,7 @@ func (h *Handler) consoles(w http.ResponseWriter, r *http.Request) { 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") if err != nil { 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) } - 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)) result, err := tmpl.ExpandHTML(nil)