From 967721068e13af2cbae0b7b94f6ff981867e6f46 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Thu, 21 Oct 2021 13:47:06 +0100 Subject: [PATCH] Alerting: Support custom annotations and labels when testing contact points Support custom annotations and labels when testing contact points --- pkg/services/ngalert/api/api_alertmanager.go | 8 +- .../api/tooling/definitions/alertmanager.go | 12 +- pkg/services/ngalert/api/tooling/post.json | 58 +- pkg/services/ngalert/api/tooling/spec.json | 50 +- pkg/services/ngalert/notifier/receivers.go | 60 +- .../alerting/api_notification_channel_test.go | 533 +++++++++++++++--- 6 files changed, 591 insertions(+), 130 deletions(-) diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index c5eeba6c855..852a20980a2 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -444,8 +444,12 @@ func contextWithTimeoutFromRequest(ctx context.Context, r *http.Request, default func newTestReceiversResult(r *notifier.TestReceiversResult) apimodels.TestReceiversResult { v := apimodels.TestReceiversResult{ - Receivers: make([]apimodels.TestReceiverResult, len(r.Receivers)), - NotifedAt: r.NotifedAt, + Alert: apimodels.TestReceiversConfigAlertParams{ + Annotations: r.Alert.Annotations, + Labels: r.Alert.Labels, + }, + Receivers: make([]apimodels.TestReceiverResult, len(r.Receivers)), + NotifiedAt: r.NotifedAt, } for ix, next := range r.Receivers { configs := make([]apimodels.TestReceiverConfigResult, len(next.Configs)) diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index 1b539438951..4c256e917e3 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -137,6 +137,8 @@ type MultiStatus struct{} // swagger:parameters RoutePostTestReceivers type TestReceiversConfigParams struct { + // in:body + Alert *TestReceiversConfigAlertParams `yaml:"alert,omitempty" json:"alert,omitempty"` // in:body Receivers []*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"` } @@ -145,10 +147,16 @@ func (c *TestReceiversConfigParams) ProcessConfig(encrypt EncryptFn) error { return processReceiverConfigs(c.Receivers, encrypt) } +type TestReceiversConfigAlertParams struct { + Annotations model.LabelSet `yaml:"annotations,omitempty" json:"annotations,omitempty"` + Labels model.LabelSet `yaml:"labels,omitempty" json:"labels,omitempty"` +} + // swagger:model type TestReceiversResult struct { - Receivers []TestReceiverResult `json:"receivers"` - NotifedAt time.Time `json:"notified_at"` + Alert TestReceiversConfigAlertParams `json:"alert"` + Receivers []TestReceiverResult `json:"receivers"` + NotifiedAt time.Time `json:"notified_at"` } // swagger:model diff --git a/pkg/services/ngalert/api/tooling/post.json b/pkg/services/ngalert/api/tooling/post.json index 699af43a8cc..5c1f9159ecc 100644 --- a/pkg/services/ngalert/api/tooling/post.json +++ b/pkg/services/ngalert/api/tooling/post.json @@ -1065,6 +1065,19 @@ "type": "array", "x-go-package": "github.com/prometheus/common/model" }, + "LabelSet": { + "additionalProperties": { + "$ref": "#/definitions/LabelValue" + }, + "description": "A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet\nmay be fully-qualified down to the point where it may resolve to a single\nMetric in the data store or not. All operations that occur within the realm\nof a LabelSet can emit a vector of Metric entities to which the LabelSet may\nmatch.", + "type": "object", + "x-go-package": "github.com/prometheus/common/model" + }, + "LabelValue": { + "title": "A LabelValue is an associated value for a LabelName.", + "type": "string", + "x-go-package": "github.com/prometheus/common/model" + }, "Labels": { "description": "Labels is a sorted set of labels. Order has to be guaranteed upon\ninstantiation.", "items": { @@ -2403,12 +2416,27 @@ "type": "object", "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" }, + "TestReceiversConfigAlertParams": { + "properties": { + "annotations": { + "$ref": "#/definitions/LabelSet" + }, + "labels": { + "$ref": "#/definitions/LabelSet" + } + }, + "type": "object", + "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + }, "TestReceiversResult": { "properties": { + "alert": { + "$ref": "#/definitions/TestReceiversConfigAlertParams" + }, "notified_at": { "format": "date-time", "type": "string", - "x-go-name": "NotifedAt" + "x-go-name": "NotifiedAt" }, "receivers": { "items": { @@ -2649,6 +2677,7 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "alertGroup": { + "description": "AlertGroup alert group", "properties": { "alerts": { "description": "alerts", @@ -2670,9 +2699,7 @@ "labels", "receiver" ], - "type": "object", - "x-go-name": "AlertGroup", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + "type": "object" }, "alertGroups": { "items": { @@ -2869,7 +2896,6 @@ "type": "array" }, "gettableSilence": { - "description": "GettableSilence gettable silence", "properties": { "comment": { "description": "comment", @@ -2921,14 +2947,17 @@ "status", "updatedAt" ], - "type": "object" + "type": "object", + "x-go-name": "GettableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "items": { "$ref": "#/definitions/gettableSilence" }, - "type": "array" + "type": "array", + "x-go-name": "GettableSilences", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "labelSet": { "additionalProperties": { @@ -3101,7 +3130,6 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "receiver": { - "description": "Receiver receiver", "properties": { "name": { "description": "name", @@ -3112,7 +3140,9 @@ "required": [ "name" ], - "type": "object" + "type": "object", + "x-go-name": "Receiver", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "silence": { "description": "Silence silence", @@ -3699,6 +3729,14 @@ "post": { "operationId": "RoutePostTestReceivers", "parameters": [ + { + "in": "body", + "name": "alert", + "schema": { + "$ref": "#/definitions/TestReceiversConfigAlertParams" + }, + "x-go-name": "Alert" + }, { "in": "body", "name": "receivers", diff --git a/pkg/services/ngalert/api/tooling/spec.json b/pkg/services/ngalert/api/tooling/spec.json index 263e3cce322..7914844df1d 100644 --- a/pkg/services/ngalert/api/tooling/spec.json +++ b/pkg/services/ngalert/api/tooling/spec.json @@ -493,6 +493,14 @@ "summary": "Test Grafana managed receivers without saving them.", "operationId": "RoutePostTestReceivers", "parameters": [ + { + "x-go-name": "Alert", + "name": "alert", + "in": "body", + "schema": { + "$ref": "#/definitions/TestReceiversConfigAlertParams" + } + }, { "x-go-name": "Receivers", "name": "receivers", @@ -2090,6 +2098,19 @@ }, "x-go-package": "github.com/prometheus/common/model" }, + "LabelSet": { + "description": "A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet\nmay be fully-qualified down to the point where it may resolve to a single\nMetric in the data store or not. All operations that occur within the realm\nof a LabelSet can emit a vector of Metric entities to which the LabelSet may\nmatch.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/LabelValue" + }, + "x-go-package": "github.com/prometheus/common/model" + }, + "LabelValue": { + "type": "string", + "title": "A LabelValue is an associated value for a LabelName.", + "x-go-package": "github.com/prometheus/common/model" + }, "Labels": { "description": "Labels is a sorted set of labels. Order has to be guaranteed upon\ninstantiation.", "type": "array", @@ -3429,13 +3450,28 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" }, + "TestReceiversConfigAlertParams": { + "type": "object", + "properties": { + "annotations": { + "$ref": "#/definitions/LabelSet" + }, + "labels": { + "$ref": "#/definitions/LabelSet" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + }, "TestReceiversResult": { "type": "object", "properties": { + "alert": { + "$ref": "#/definitions/TestReceiversConfigAlertParams" + }, "notified_at": { "type": "string", "format": "date-time", - "x-go-name": "NotifedAt" + "x-go-name": "NotifiedAt" }, "receivers": { "type": "array", @@ -3675,6 +3711,7 @@ "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "alertGroup": { + "description": "AlertGroup alert group", "type": "object", "required": [ "alerts", @@ -3697,8 +3734,6 @@ "$ref": "#/definitions/receiver" } }, - "x-go-name": "AlertGroup", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/alertGroup" }, "alertGroups": { @@ -3899,7 +3934,6 @@ "$ref": "#/definitions/gettableAlerts" }, "gettableSilence": { - "description": "GettableSilence gettable silence", "type": "object", "required": [ "comment", @@ -3952,14 +3986,17 @@ "x-go-name": "UpdatedAt" } }, + "x-go-name": "GettableSilence", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/gettableSilence" }, "gettableSilences": { - "description": "GettableSilences gettable silences", "type": "array", "items": { "$ref": "#/definitions/gettableSilence" }, + "x-go-name": "GettableSilences", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/gettableSilences" }, "labelSet": { @@ -4134,7 +4171,6 @@ "$ref": "#/definitions/postableSilence" }, "receiver": { - "description": "Receiver receiver", "type": "object", "required": [ "name" @@ -4146,6 +4182,8 @@ "x-go-name": "Name" } }, + "x-go-name": "Receiver", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models", "$ref": "#/definitions/receiver" }, "silence": { diff --git a/pkg/services/ngalert/notifier/receivers.go b/pkg/services/ngalert/notifier/receivers.go index 4985183d102..6fbcd9f6875 100644 --- a/pkg/services/ngalert/notifier/receivers.go +++ b/pkg/services/ngalert/notifier/receivers.go @@ -24,6 +24,7 @@ var ( ) type TestReceiversResult struct { + Alert types.Alert Receivers []TestReceiverResult NotifedAt time.Time } @@ -61,19 +62,7 @@ func (e ReceiverTimeoutError) Error() string { func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigParams) (*TestReceiversResult, error) { // now represents the start time of the test now := time.Now() - testAlert := &types.Alert{ - Alert: model.Alert{ - Labels: model.LabelSet{ - model.LabelName("alertname"): "TestAlert", - model.LabelName("instance"): "Grafana", - }, - Annotations: model.LabelSet{ - model.LabelName("summary"): "Notification test", - }, - StartsAt: now, - }, - UpdatedAt: now, - } + testAlert := newTestAlert(c, now, now) // we must set a group key that is unique per test as some receivers use this key to deduplicate alerts ctx = notify.WithGroupKey(ctx, testAlert.Labels.String()+now.String()) @@ -97,7 +86,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei Error error } - newTestReceiversResult := func(results []result, notifiedAt time.Time) *TestReceiversResult { + newTestReceiversResult := func(alert types.Alert, results []result, notifiedAt time.Time) *TestReceiversResult { m := make(map[string]TestReceiverResult) for _, receiver := range c.Receivers { // set up the result for this receiver @@ -122,6 +111,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei m[next.ReceiverName] = tmp } v := new(TestReceiversResult) + v.Alert = alert v.Receivers = make([]TestReceiverResult, 0, len(c.Receivers)) v.NotifedAt = notifiedAt for _, next := range m { @@ -165,7 +155,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei } if len(jobs) == 0 { - return newTestReceiversResult(invalid, now), nil + return newTestReceiversResult(testAlert, invalid, now), nil } numWorkers := maxTestReceiversWorkers @@ -188,7 +178,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei Config: next.Config, ReceiverName: next.ReceiverName, } - if _, err := next.Notifier.Notify(ctx, testAlert); err != nil { + if _, err := next.Notifier.Notify(ctx, &testAlert); err != nil { v.Error = err } resultCh <- v @@ -204,7 +194,43 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei results = append(results, next) } - return newTestReceiversResult(append(invalid, results...), now), nil + return newTestReceiversResult(testAlert, append(invalid, results...), now), nil +} + +func newTestAlert(c apimodels.TestReceiversConfigParams, startsAt, updatedAt time.Time) types.Alert { + var ( + defaultAnnotations = model.LabelSet{ + "summary": "Notification test", + } + defaultLabels = model.LabelSet{ + "alertname": "TestAlert", + "instance": "Grafana", + } + ) + + alert := types.Alert{ + Alert: model.Alert{ + Labels: defaultLabels, + Annotations: defaultAnnotations, + StartsAt: startsAt, + }, + UpdatedAt: updatedAt, + } + + if c.Alert != nil { + if c.Alert.Annotations != nil { + for k, v := range c.Alert.Annotations { + alert.Annotations[k] = v + } + } + if c.Alert.Labels != nil { + for k, v := range c.Alert.Labels { + alert.Labels[k] = v + } + } + } + + return alert } func processNotifierError(config *apimodels.PostableGrafanaReceiver, err error) error { diff --git a/pkg/tests/api/alerting/api_notification_channel_test.go b/pkg/tests/api/alerting/api_notification_channel_test.go index db844620d63..41e9dc90aa5 100644 --- a/pkg/tests/api/alerting/api_notification_channel_test.go +++ b/pkg/tests/api/alerting/api_notification_channel_test.go @@ -92,14 +92,14 @@ func TestTestReceivers(t *testing.T) { "name":"receiver-1", "grafana_managed_receiver_configs": [ { - "uid":"", - "name":"receiver-1", - "type":"email", - "disableResolveMessage":false, - "settings":{ + "uid": "", + "name": "receiver-1", + "type": "email", + "disableResolveMessage": false, + "settings": { "addresses":"example@email.com" }, - "secureFields":{} + "secureFields": {} } ] }] @@ -109,22 +109,39 @@ func TestTestReceivers(t *testing.T) { require.NoError(t, err) }) - var result apimodels.TestReceiversResult - require.NoError(t, json.NewDecoder(resp.Body).Decode(&result)) + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) require.Len(t, result.Receivers, 1) require.Len(t, result.Receivers[0].Configs, 1) - require.Equal(t, apimodels.TestReceiversResult{ - Receivers: []apimodels.TestReceiverResult{{ - Name: "receiver-1", - Configs: []apimodels.TestReceiverConfigResult{{ - Name: "receiver-1", - UID: result.Receivers[0].Configs[0].UID, - Status: "ok", - }}, - }}, - NotifedAt: result.NotifedAt, - }, result) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "Notification test" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "ok" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) require.Len(t, mockEmails.emails, 1) require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To) @@ -160,38 +177,54 @@ func TestTestReceivers(t *testing.T) { "name":"receiver-1", "grafana_managed_receiver_configs": [ { - "uid":"", - "name":"receiver-1", - "type":"email", - "disableResolveMessage":false, - "settings":{}, - "secureFields":{} + "uid": "", + "name": "receiver-1", + "type": "email", + "disableResolveMessage": false, + "settings": {}, + "secureFields": {} } ] }] }`, http.StatusBadRequest) - b, err := ioutil.ReadAll(resp.Body) - require.NoError(t, err) t.Cleanup(func() { require.NoError(t, resp.Body.Close()) }) + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + var result apimodels.TestReceiversResult require.NoError(t, json.Unmarshal(b, &result)) require.Len(t, result.Receivers, 1) require.Len(t, result.Receivers[0].Configs, 1) - require.Equal(t, apimodels.TestReceiversResult{ - Receivers: []apimodels.TestReceiverResult{{ - Name: "receiver-1", - Configs: []apimodels.TestReceiverConfigResult{{ - Name: "receiver-1", - UID: result.Receivers[0].Configs[0].UID, - Status: "failed", - Error: "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings", - }}, - }}, - NotifedAt: result.NotifedAt, - }, result) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "Notification test" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "failed", + "error": "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) }) t.Run("assert timed out receiver returns 408 Request Timeout", func(t *testing.T) { @@ -225,14 +258,14 @@ func TestTestReceivers(t *testing.T) { "name":"receiver-1", "grafana_managed_receiver_configs": [ { - "uid":"", - "name":"receiver-1", - "type":"email", - "disableResolveMessage":false, - "settings":{ + "uid": "", + "name": "receiver-1", + "type": "email", + "disableResolveMessage": false, + "settings": { "addresses":"example@email.com" }, - "secureFields":{} + "secureFields": {} } ] }] @@ -248,23 +281,40 @@ func TestTestReceivers(t *testing.T) { }) require.Equal(t, http.StatusRequestTimeout, resp.StatusCode) - var result apimodels.TestReceiversResult - require.NoError(t, json.NewDecoder(resp.Body).Decode(&result)) + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) require.Len(t, result.Receivers, 1) require.Len(t, result.Receivers[0].Configs, 1) - require.Equal(t, apimodels.TestReceiversResult{ - Receivers: []apimodels.TestReceiverResult{{ - Name: "receiver-1", - Configs: []apimodels.TestReceiverConfigResult{{ - Name: "receiver-1", - UID: result.Receivers[0].Configs[0].UID, - Status: "failed", - Error: "the receiver timed out: context deadline exceeded", - }}, - }}, - NotifedAt: result.NotifedAt, - }, result) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "Notification test" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "failed", + "error": "the receiver timed out: context deadline exceeded" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) }) t.Run("assert multiple different errors returns 207 Multi Status", func(t *testing.T) { @@ -298,26 +348,26 @@ func TestTestReceivers(t *testing.T) { "name":"receiver-1", "grafana_managed_receiver_configs": [ { - "uid":"", - "name":"receiver-1", - "type":"email", - "disableResolveMessage":false, - "settings":{}, - "secureFields":{} + "uid": "", + "name": "receiver-1", + "type": "email", + "disableResolveMessage": false, + "settings": {}, + "secureFields": {} } ] }, { "name":"receiver-2", "grafana_managed_receiver_configs": [ { - "uid":"", - "name":"receiver-2", - "type":"email", - "disableResolveMessage":false, - "settings":{ + "uid": "", + "name": "receiver-2", + "type": "email", + "disableResolveMessage": false, + "settings": { "addresses":"example@email.com" }, - "secureFields":{} + "secureFields": {} } ] }] @@ -333,32 +383,329 @@ func TestTestReceivers(t *testing.T) { }) require.Equal(t, http.StatusMultiStatus, resp.StatusCode) - var result apimodels.TestReceiversResult - require.NoError(t, json.NewDecoder(resp.Body).Decode(&result)) + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) require.Len(t, result.Receivers, 2) require.Len(t, result.Receivers[0].Configs, 1) require.Len(t, result.Receivers[1].Configs, 1) - require.Equal(t, apimodels.TestReceiversResult{ - Receivers: []apimodels.TestReceiverResult{{ - Name: "receiver-1", - Configs: []apimodels.TestReceiverConfigResult{{ - Name: "receiver-1", - UID: result.Receivers[0].Configs[0].UID, - Status: "failed", - Error: "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings", - }}, - }, { - Name: "receiver-2", - Configs: []apimodels.TestReceiverConfigResult{{ - Name: "receiver-2", - UID: result.Receivers[1].Configs[0].UID, - Status: "failed", - Error: "the receiver timed out: context deadline exceeded", - }}, - }}, - NotifedAt: result.NotifedAt, - }, result) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "Notification test" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "failed", + "error": "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings" + } + ] + }, { + "name":"receiver-2", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-2", + "uid": "%s", + "status": "failed", + "error": "the receiver timed out: context deadline exceeded" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.Receivers[1].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) + }) +} + +func TestTestReceiversAlertCustomization(t *testing.T) { + t.Run("assert custom annotations and labels are sent", func(t *testing.T) { + // Setup Grafana and its Database + dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + }) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) + store.Bus = bus.GetBus() + + createUser(t, store, models.CreateUserCommand{ + DefaultOrgRole: string(models.ROLE_EDITOR), + Login: "grafana", + Password: "password", + }) + + oldEmailBus := bus.GetHandlerCtx("SendEmailCommandSync") + mockEmails := &mockEmailHandler{} + bus.AddHandlerCtx("", mockEmails.sendEmailCommandHandlerSync) + t.Cleanup(func() { + bus.AddHandlerCtx("", oldEmailBus) + }) + + testReceiversURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/receivers/test", grafanaListedAddr) + // nolint + resp := postRequest(t, testReceiversURL, `{ + "alert": { + "annotations": { + "annotation1": "value1" + }, + "labels": { + "label1": "value1" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "uid":"", + "name":"receiver-1", + "type":"email", + "disableResolveMessage":false, + "settings":{ + "addresses":"example@email.com" + }, + "secureFields":{} + } + ] + }] +}`, http.StatusOK) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) + require.Len(t, result.Receivers, 1) + require.Len(t, result.Receivers[0].Configs, 1) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "annotation1": "value1", + "summary": "Notification test" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana", + "label1": "value1" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "ok" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) + + require.Len(t, mockEmails.emails, 1) + require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To) + }) + + t.Run("assert custom annotations can replace default annotations", func(t *testing.T) { + // Setup Grafana and its Database + dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + }) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) + store.Bus = bus.GetBus() + + createUser(t, store, models.CreateUserCommand{ + DefaultOrgRole: string(models.ROLE_EDITOR), + Login: "grafana", + Password: "password", + }) + + oldEmailBus := bus.GetHandlerCtx("SendEmailCommandSync") + mockEmails := &mockEmailHandler{} + bus.AddHandlerCtx("", mockEmails.sendEmailCommandHandlerSync) + t.Cleanup(func() { + bus.AddHandlerCtx("", oldEmailBus) + }) + + testReceiversURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/receivers/test", grafanaListedAddr) + // nolint + resp := postRequest(t, testReceiversURL, `{ + "alert": { + "annotations": { + "summary": "This is a custom annotation" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "uid":"", + "name":"receiver-1", + "type":"email", + "disableResolveMessage":false, + "settings":{ + "addresses":"example@email.com" + }, + "secureFields":{} + } + ] + }] +}`, http.StatusOK) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) + require.Len(t, result.Receivers, 1) + require.Len(t, result.Receivers[0].Configs, 1) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "This is a custom annotation" + }, + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "ok" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) + + require.Len(t, mockEmails.emails, 1) + require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To) + }) + + t.Run("assert custom labels can replace default label", func(t *testing.T) { + // Setup Grafana and its Database + dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + }) + + grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path) + store.Bus = bus.GetBus() + + createUser(t, store, models.CreateUserCommand{ + DefaultOrgRole: string(models.ROLE_EDITOR), + Login: "grafana", + Password: "password", + }) + + oldEmailBus := bus.GetHandlerCtx("SendEmailCommandSync") + mockEmails := &mockEmailHandler{} + bus.AddHandlerCtx("", mockEmails.sendEmailCommandHandlerSync) + t.Cleanup(func() { + bus.AddHandlerCtx("", oldEmailBus) + }) + + testReceiversURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/receivers/test", grafanaListedAddr) + // nolint + resp := postRequest(t, testReceiversURL, `{ + "alert": { + "labels": { + "alertname": "This is a custom label" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "uid":"", + "name":"receiver-1", + "type":"email", + "disableResolveMessage":false, + "settings":{ + "addresses":"example@email.com" + }, + "secureFields":{} + } + ] + }] +}`, http.StatusOK) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + var result apimodels.TestReceiversResult + require.NoError(t, json.Unmarshal(b, &result)) + require.Len(t, result.Receivers, 1) + require.Len(t, result.Receivers[0].Configs, 1) + + expectedJSON := fmt.Sprintf(`{ + "alert": { + "annotations": { + "summary": "Notification test" + }, + "labels": { + "alertname": "This is a custom label", + "instance": "Grafana" + } + }, + "receivers": [{ + "name":"receiver-1", + "grafana_managed_receiver_configs": [ + { + "name": "receiver-1", + "uid": "%s", + "status": "ok" + } + ] + }], + "notified_at": "%s" +}`, + result.Receivers[0].Configs[0].UID, + result.NotifiedAt.Format(time.RFC3339Nano)) + require.JSONEq(t, expectedJSON, string(b)) + + require.Len(t, mockEmails.emails, 1) + require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To) }) }