Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alertmanager: Add default Grafana template string #8465

Merged
merged 18 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions pkg/alertmanager/alertmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/alerting/notify/nfstatus"
alertingReceivers "github.com/grafana/alerting/receivers"
alertingTemplates "github.com/grafana/alerting/templates"
"github.com/grafana/dskit/flagext"
"github.com/pkg/errors"
"github.com/prometheus/alertmanager/api"
Expand Down Expand Up @@ -356,7 +357,7 @@ func clusterWait(position func() int, timeout time.Duration) func() time.Duratio
}

// ApplyConfig applies a new configuration to an Alertmanager.
func (am *Alertmanager) ApplyConfig(conf *definition.PostableApiAlertingConfig, rawCfg string) error {
func (am *Alertmanager) ApplyConfig(conf *definition.PostableApiAlertingConfig, rawCfg string, tmplExternalURL *url.URL) error {
templateFiles := make([]string, len(conf.Templates))
for i, t := range conf.Templates {
templateFilepath, err := safeTemplateFilepath(filepath.Join(am.cfg.TenantDataDir, templatesDir), t)
Expand All @@ -371,7 +372,7 @@ func (am *Alertmanager) ApplyConfig(conf *definition.PostableApiAlertingConfig,
if err != nil {
return err
}
tmpl.ExternalURL = am.cfg.ExternalURL
tmpl.ExternalURL = tmplExternalURL

cfg := definition.GrafanaToUpstreamConfig(conf)
am.api.Update(&cfg, func(_ model.LabelSet) {})
Expand All @@ -397,7 +398,7 @@ func (am *Alertmanager) ApplyConfig(conf *definition.PostableApiAlertingConfig,
return d + waitFunc()
}

integrationsMap, err := am.buildIntegrationsMap(conf.Receivers, tmpl)
integrationsMap, err := am.buildIntegrationsMap(conf.Receivers, tmpl, templateFiles)
if err != nil {
return err
}
Expand Down Expand Up @@ -495,7 +496,7 @@ func (am *Alertmanager) getFullState() (*clusterpb.FullState, error) {
}

// buildIntegrationsMap builds a map of name to the list of integration notifiers off of a list of receiver config.
func (am *Alertmanager) buildIntegrationsMap(nc []*definition.PostableApiReceiver, tmpl *template.Template) (map[string][]*nfstatus.Integration, error) {
func (am *Alertmanager) buildIntegrationsMap(nc []*definition.PostableApiReceiver, tmpl *template.Template, templateFiles []string) (map[string][]*nfstatus.Integration, error) {
// Create a firewall binded to the per-tenant config.
firewallDialer := util_net.NewFirewallDialer(newFirewallDialerConfigProvider(am.cfg.UserID, am.cfg.Limits))

Expand All @@ -513,12 +514,24 @@ func (am *Alertmanager) buildIntegrationsMap(nc []*definition.PostableApiReceive
return notifier
}

var gTmpl *template.Template
integrationsMap := make(map[string][]*nfstatus.Integration, len(nc))
for _, rcv := range nc {
var integrations []*nfstatus.Integration
var err error
if rcv.Type() == definition.GrafanaReceiverType {
integrations, err = buildGrafanaReceiverIntegrations(rcv, tmpl, am.logger)
// Create the Grafana template struct if it has not already been created.
if gTmpl == nil {
gTmpl, err = template.FromGlobs(templateFiles, WithCustomFunctions(am.cfg.UserID))
if err != nil {
return nil, err
}
if err := gTmpl.Parse(strings.NewReader(alertingTemplates.DefaultTemplateString)); err != nil {
return nil, err
}
gTmpl.ExternalURL = tmpl.ExternalURL
}
integrations, err = buildGrafanaReceiverIntegrations(rcv, gTmpl, am.logger)
} else {
integrations, err = buildReceiverIntegrations(rcv.Receiver, tmpl, firewallDialer, am.logger, nw)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/alertmanager/alertmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ route:

cfg, err := definition.LoadCompat([]byte(cfgRaw))
require.NoError(t, err)
require.NoError(t, am.ApplyConfig(cfg, cfgRaw))
require.NoError(t, am.ApplyConfig(cfg, cfgRaw, &url.URL{}))

now := time.Now()

Expand Down Expand Up @@ -177,7 +177,7 @@ route:

cfg, err := definition.LoadCompat([]byte(cfgRaw))
require.NoError(t, err)
require.NoError(t, am.ApplyConfig(cfg, cfgRaw))
require.NoError(t, am.ApplyConfig(cfg, cfgRaw, &url.URL{}))

now := time.Now()
inputAlerts := []*types.Alert{
Expand Down Expand Up @@ -466,7 +466,7 @@ route:

cfg, err := definition.LoadCompat([]byte(cfgRaw))
require.NoError(t, err)
require.NoError(t, am.ApplyConfig(cfg, cfgRaw))
require.NoError(t, am.ApplyConfig(cfg, cfgRaw, &url.URL{}))

doGetReceivers := func() []alertingmodels.Receiver {
rr := httptest.NewRecorder()
Expand Down
111 changes: 83 additions & 28 deletions pkg/alertmanager/alertspb/alerts.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/alertmanager/alertspb/alerts.proto
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ message GrafanaAlertConfigDesc {
int64 created_at_timestamp = 5;
bool default = 7;
bool promoted = 8;
string external_url = 9;
}
3 changes: 2 additions & 1 deletion pkg/alertmanager/alertspb/compat.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ func ToProto(cfg string, templates map[string]string, user string) AlertConfigDe
}

// ToGrafanaProto transforms a Grafana Alertmanager config to a GrafanaAlertConfigDesc.
func ToGrafanaProto(cfg, user, hash string, createdAtTimestamp int64, isDefault, isPromoted bool) GrafanaAlertConfigDesc {
func ToGrafanaProto(cfg, user, hash string, createdAtTimestamp int64, isDefault, isPromoted bool, externalURL string) GrafanaAlertConfigDesc {
return GrafanaAlertConfigDesc{
User: user,
RawConfig: cfg,
Hash: hash,
CreatedAtTimestamp: createdAtTimestamp,
Default: isDefault,
Promoted: isPromoted,
ExternalUrl: externalURL,
}
}

Expand Down
5 changes: 4 additions & 1 deletion pkg/alertmanager/api_grafana.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type UserGrafanaConfig struct {
CreatedAt int64 `json:"created"`
Default bool `json:"default"`
Promoted bool `json:"promoted"`
ExternalURL string `json:"external_url"`
}

func (gc *UserGrafanaConfig) Validate() error {
Expand Down Expand Up @@ -290,6 +291,8 @@ func (am *MultitenantAlertmanager) GetUserGrafanaConfig(w http.ResponseWriter, r
Hash: cfg.Hash,
CreatedAt: cfg.CreatedAtTimestamp,
Default: cfg.Default,
Promoted: cfg.Promoted,
ExternalURL: cfg.ExternalUrl,
},
})
}
Expand Down Expand Up @@ -329,7 +332,7 @@ func (am *MultitenantAlertmanager) SetUserGrafanaConfig(w http.ResponseWriter, r
return
}

cfgDesc := alertspb.ToGrafanaProto(string(rawCfg), userID, cfg.Hash, cfg.CreatedAt, cfg.Default, cfg.Promoted)
cfgDesc := alertspb.ToGrafanaProto(string(rawCfg), userID, cfg.Hash, cfg.CreatedAt, cfg.Default, cfg.Promoted, cfg.ExternalURL)
err = am.store.SetGrafanaAlertConfig(r.Context(), cfgDesc)
if err != nil {
level.Error(logger).Log("msg", errStoringGrafanaConfig, "err", err.Error())
Expand Down
10 changes: 7 additions & 3 deletions pkg/alertmanager/api_grafana_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,14 @@ func TestMultitenantAlertmanager_GetUserGrafanaConfig(t *testing.T) {
logger: test.NewTestingLogger(t),
}

externalURL := "http://test.grafana.com"
require.NoError(t, alertstore.SetGrafanaAlertConfig(context.Background(), alertspb.GrafanaAlertConfigDesc{
User: "test_user",
RawConfig: testGrafanaConfig,
Hash: "bb788eaa294c05ec556c1ed87546b7a9",
CreatedAtTimestamp: now,
Default: false,
ExternalUrl: externalURL,
}))

require.Len(t, storage.Objects(), 1)
Expand Down Expand Up @@ -231,11 +233,12 @@ func TestMultitenantAlertmanager_GetUserGrafanaConfig(t *testing.T) {
"configuration_hash": "bb788eaa294c05ec556c1ed87546b7a9",
"created": %d,
"default": false,
"promoted": false
"promoted": false,
"external_url": %q
},
"status": "success"
}
`, testGrafanaConfig, now)
`, testGrafanaConfig, now, externalURL)

require.JSONEq(t, json, string(body))
require.Equal(t, "application/json", rec.Header().Get("Content-Type"))
Expand Down Expand Up @@ -347,7 +350,8 @@ func TestMultitenantAlertmanager_SetUserGrafanaConfig(t *testing.T) {
"configuration_hash": "ChEKBW5mbG9nEghzb21lZGF0YQ==",
"created": 12312414343,
"default": false,
"promoted": true
"promoted": true,
"external_url": "http://test.grafana.com"
}
`, testGrafanaConfig)
req.Body = io.NopCloser(strings.NewReader(json))
Expand Down
Loading
Loading