Alerting: Template preview enhancements (#103817)

API Changes:

- Fixes validation in template CRUD API to be closer to how the running 
  alertmanager will use the template. Should remove some incorrect 
  validation errors.

- Adds some missing default placeholder labels to receiver testing that 
  are used during template testing but missing during receiver testing


Template Preview:

- Replaced basic preview with a readonly CodeEditor for better whitespace 
  and alignment clarity (also adds support for future syntax highlighting 
  in template previews for upcoming webhook payload templates)

Template Selector (Receiver Form):

- Refactored to use same components as Template editor for preview.
- Fixed preview to work with multi-definition templates
- Fixed copy to correctly copy the template contents instead of 
  {{ template "<name>" . }}.

Template Editor:

- Fixed detection of when to display functions vs snippets in multi-line 
  expressions
This commit is contained in:
Matthew Jacobson
2025-04-11 09:27:19 -04:00
committed by GitHub
parent 2c3422fc5c
commit 9f9c4b3da3
15 changed files with 172 additions and 102 deletions
@@ -2,10 +2,8 @@ package definitions
import (
"fmt"
tmplhtml "html/template"
"regexp"
"strings"
tmpltext "text/template"
"github.com/prometheus/alertmanager/template"
"gopkg.in/yaml.v3"
@@ -35,17 +33,12 @@ func (t *NotificationTemplate) Validate() error {
t.Template = content
// Validate template contents. We try to stick as close to what will actually happen when the templates are parsed
// by the alertmanager as possible. That means parsing with both the text and html parsers and making sure we set
// the template name and options.
ttext := tmpltext.New(t.Name).Option("missingkey=zero")
ttext.Funcs(tmpltext.FuncMap(template.DefaultFuncs))
if _, err := ttext.Parse(t.Template); err != nil {
return fmt.Errorf("invalid template: %w", err)
// by the alertmanager as possible.
tmpl, err := template.New()
if err != nil {
return fmt.Errorf("failed to create template: %w", err)
}
thtml := tmplhtml.New(t.Name).Option("missingkey=zero")
thtml.Funcs(tmplhtml.FuncMap(template.DefaultFuncs))
if _, err := thtml.Parse(t.Template); err != nil {
if err := tmpl.Parse(strings.NewReader(t.Template)); err != nil {
return fmt.Errorf("invalid template: %w", err)
}
@@ -442,7 +442,6 @@ func TestValidateNotificationTemplates(t *testing.T) {
expError: errors.New("invalid template: template: Different name than definition:1: template: multiple definition of template \"Alert Instance Template\""),
},
{
// This is fine as long as the template name is different from the definition, it just ignores the extra text.
name: "Extra text outside definition block - different template name and definition",
template: NotificationTemplate{
Name: "Different name than definition",
@@ -452,16 +451,16 @@ func TestValidateNotificationTemplates(t *testing.T) {
expContent: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}[what is this?]`,
expError: nil,
},
// This test used to error because our template code parsed the template with the filename as template name.
// However, we have since moved away from this. We keep this test to ensure we don't regress.
{
// This is NOT fine as the template name is the same as the definition.
// GO template parser will treat it as if it's wrapped in {{ define "Alert Instance Template" }}, thus creating a duplicate definition.
name: "Extra text outside definition block - same template name and definition",
template: NotificationTemplate{
Name: "Alert Instance Template",
Template: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}[what is this?]`,
Provenance: "test",
},
expError: errors.New("invalid template: template: Alert Instance Template:1: template: multiple definition of template \"Alert Instance Template\""),
expContent: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}[what is this?]`,
},
}