Skip to content

Commit 0fb3072

Browse files
authored
Alertmanager: Add ExtraData field to experimental pre-notification hook (#12481)
#### What this PR does Adds another field `ExtraData` to `hookData` which is a `json.RawMessage` - this additional data is then passed along via the context. #### Checklist - [x] Tests updated. - [x] Documentation added. - [x] `CHANGELOG.md` updated - the order of entries should be `[CHANGE]`, `[FEATURE]`, `[ENHANCEMENT]`, `[BUGFIX]`. If changelog entry is not needed, please add the `changelog-not-needed` label to the PR. - [x] [`about-versioning.md`](https://github.com/grafana/mimir/blob/main/docs/sources/mimir/configure/about-versioning.md) updated with experimental features.
1 parent 9657d59 commit 0fb3072

File tree

2 files changed

+57
-13
lines changed

2 files changed

+57
-13
lines changed

pkg/alertmanager/notify_hooks_notifier.go

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,15 @@ func newNotifyHooksMetrics(reg prometheus.Registerer) *notifyHooksMetrics {
105105
}
106106

107107
func (n *notifyHooksNotifier) Notify(ctx context.Context, alerts ...*types.Alert) (bool, error) {
108-
newAlerts := n.apply(ctx, alerts)
108+
result := n.apply(ctx, alerts)
109109

110-
return n.upstream.Notify(ctx, newAlerts...)
110+
// Add additional data from pre-notify hook to the context
111+
ctxWithData := withExtraData(ctx, result.ExtraData)
112+
113+
return n.upstream.Notify(ctxWithData, result.Alerts...)
111114
}
112115

113-
func (n *notifyHooksNotifier) apply(ctx context.Context, alerts []*types.Alert) []*types.Alert {
116+
func (n *notifyHooksNotifier) apply(ctx context.Context, alerts []*types.Alert) *hookData {
114117
l := n.logger
115118

116119
receiver, _ := notify.ReceiverName(ctx)
@@ -122,19 +125,23 @@ func (n *notifyHooksNotifier) apply(ctx context.Context, alerts []*types.Alert)
122125
url := n.limits.AlertmanagerNotifyHookURL(n.user)
123126
if url == "" {
124127
level.Debug(l).Log("msg", "Notify hooks not applied, no URL configured")
125-
return alerts
128+
return &hookData{
129+
Alerts: alerts,
130+
}
126131
}
127132

128133
receivers := n.limits.AlertmanagerNotifyHookReceivers(n.user)
129134
if len(receivers) > 0 && !slices.Contains(receivers, receiver) {
130135
level.Debug(l).Log("msg", "Notify hooks not applied, not enabled for receiver")
131-
return alerts
136+
return &hookData{
137+
Alerts: alerts,
138+
}
132139
}
133140

134141
timeout := n.limits.AlertmanagerNotifyHookTimeout(n.user)
135142

136143
start := time.Now()
137-
newAlerts, code, err := n.invoke(ctx, l, url, timeout, alerts)
144+
result, code, err := n.invoke(ctx, l, url, timeout, alerts)
138145

139146
duration := time.Since(start)
140147
n.metrics.hookDuration.Observe(float64(duration))
@@ -156,12 +163,14 @@ func (n *notifyHooksNotifier) apply(ctx context.Context, alerts []*types.Alert)
156163
n.metrics.hookFailed.WithLabelValues(status).Inc()
157164
level.Error(l).Log("msg", "Notify hooks failed", "err", err)
158165
}
159-
return alerts
166+
return &hookData{
167+
Alerts: alerts,
168+
}
160169
}
161170

162171
level.Debug(l).Log("msg", "Notify hooks applied successfully")
163172

164-
return newAlerts
173+
return result
165174
}
166175

167176
// hookData is the payload we send and receive from the notification hook.
@@ -170,6 +179,18 @@ type hookData struct {
170179
Status string `json:"status"`
171180
Alerts []*types.Alert `json:"alerts"`
172181
GroupLabels model.LabelSet `json:"groupLabels"`
182+
183+
ExtraData json.RawMessage `json:"extraData,omitempty"`
184+
}
185+
186+
type extraDataKey int
187+
188+
const (
189+
ExtraDataKey extraDataKey = iota
190+
)
191+
192+
func withExtraData(ctx context.Context, extraData json.RawMessage) context.Context {
193+
return context.WithValue(ctx, ExtraDataKey, extraData)
173194
}
174195

175196
func (n *notifyHooksNotifier) getData(ctx context.Context, l log.Logger, alerts []*types.Alert) *hookData {
@@ -190,7 +211,7 @@ func (n *notifyHooksNotifier) getData(ctx context.Context, l log.Logger, alerts
190211
}
191212
}
192213

193-
func (n *notifyHooksNotifier) invoke(ctx context.Context, l log.Logger, url string, timeout time.Duration, alerts []*types.Alert) ([]*types.Alert, int, error) {
214+
func (n *notifyHooksNotifier) invoke(ctx context.Context, l log.Logger, url string, timeout time.Duration, alerts []*types.Alert) (*hookData, int, error) {
194215
logger, ctx := spanlogger.New(ctx, l, tracer, "NotifyHooksNotifier.Invoke")
195216
defer logger.Finish()
196217

@@ -240,5 +261,5 @@ func (n *notifyHooksNotifier) invoke(ctx context.Context, l log.Logger, url stri
240261
return nil, 0, err
241262
}
242263

243-
return result.Alerts, 0, nil
264+
return &result, 0, nil
244265
}

pkg/alertmanager/notify_hooks_notifier_test.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package alertmanager
44

55
import (
66
"context"
7+
"encoding/json"
78
"fmt"
89
"io"
910
"net/http"
@@ -24,10 +25,12 @@ import (
2425
)
2526

2627
type fakeNotifier struct {
27-
calls [][]*types.Alert
28+
ctxForTesting context.Context
29+
calls [][]*types.Alert
2830
}
2931

30-
func (m *fakeNotifier) Notify(_ context.Context, alerts ...*types.Alert) (bool, error) {
32+
func (m *fakeNotifier) Notify(ctx context.Context, alerts ...*types.Alert) (bool, error) {
33+
m.ctxForTesting = ctx
3134
m.calls = append(m.calls, alerts)
3235
return false, nil
3336
}
@@ -182,7 +185,9 @@ func TestNotifyHooksNotifier(t *testing.T) {
182185
`"endsAt":"0001-01-01T00:00:00Z",` +
183186
`"generatorURL":"",` +
184187
`"UpdatedAt":"0001-01-01T00:00:00Z",` +
185-
`"Timeout":false}]}`
188+
`"Timeout":false}],` +
189+
`"extraData": {"foo": "bar"}` +
190+
`}`
186191

187192
t.Run("hook invoked", func(t *testing.T) {
188193
f := newTestHooksFixture(t, http.StatusOK, okResponse)
@@ -193,6 +198,24 @@ func TestNotifyHooksNotifier(t *testing.T) {
193198
require.Equal(t, [][]*types.Alert{makeAlert("changed")}, f.upstream.calls)
194199
f.assertMetricsSuccess(t, 1, 0)
195200
})
201+
t.Run("hook invoked with extra data", func(t *testing.T) {
202+
f := newTestHooksFixture(t, http.StatusOK, okResponse)
203+
ctx := makeContext()
204+
205+
_, err := f.notifier.Notify(ctx, makeAlert("foo")...)
206+
require.NoError(t, err)
207+
208+
type testExtraData struct {
209+
Foo string `json:"foo"`
210+
}
211+
212+
extraDataRaw := f.upstream.ctxForTesting.Value(ExtraDataKey).(json.RawMessage)
213+
extraData := testExtraData{}
214+
err = json.Unmarshal(extraDataRaw, &extraData)
215+
require.NoError(t, err)
216+
217+
require.Equal(t, "bar", extraData.Foo)
218+
})
196219
t.Run("hook not invoked when empty url configured", func(t *testing.T) {
197220
f := newTestHooksFixture(t, http.StatusOK, okResponse)
198221
f.limits.url = ""

0 commit comments

Comments
 (0)