@@ -5,13 +5,17 @@ import (
5
5
"encoding/base64"
6
6
"encoding/json"
7
7
"errors"
8
+ "fmt"
9
+ "hash/fnv"
8
10
"slices"
9
11
12
+ "github.com/grafana/grafana/pkg/apimachinery/errutil"
10
13
"github.com/grafana/grafana/pkg/apimachinery/identity"
11
14
"github.com/grafana/grafana/pkg/infra/log"
12
15
"github.com/grafana/grafana/pkg/services/accesscontrol"
13
16
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
14
17
"github.com/grafana/grafana/pkg/services/ngalert/models"
18
+ "github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation"
15
19
"github.com/grafana/grafana/pkg/services/secrets"
16
20
)
17
21
22
26
ErrNotFound = errors .New ("not found" ) // TODO: convert to errutil
23
27
)
24
28
29
+ var (
30
+ ErrReceiverInUse = errutil .Conflict ("alerting.notifications.receiver.used" , errutil .WithPublicMessage ("Receiver is used by one or many notification policies" ))
31
+ ErrVersionConflict = errutil .Conflict ("alerting.notifications.receiver.conflict" )
32
+ )
33
+
25
34
// ReceiverService is the service for managing alertmanager receivers.
26
35
type ReceiverService struct {
27
36
ac accesscontrol.AccessControl
@@ -30,6 +39,7 @@ type ReceiverService struct {
30
39
encryptionService secrets.Service
31
40
xact transactionManager
32
41
log log.Logger
42
+ validator validation.ProvenanceStatusTransitionValidator
33
43
}
34
44
35
45
type configStore interface {
@@ -39,6 +49,7 @@ type configStore interface {
39
49
40
50
type provisoningStore interface {
41
51
GetProvenances (ctx context.Context , org int64 , resourceType string ) (map [string ]models.Provenance , error )
52
+ DeleteProvenance (ctx context.Context , o models.Provisionable , org int64 ) error
42
53
}
43
54
44
55
type transactionManager interface {
@@ -60,6 +71,7 @@ func NewReceiverService(
60
71
encryptionService : encryptionService ,
61
72
xact : xact ,
62
73
log : log ,
74
+ validator : validation .ValidateProvenanceRelaxed ,
63
75
}
64
76
}
65
77
@@ -119,7 +131,7 @@ func (rs *ReceiverService) GetReceiver(ctx context.Context, q models.GetReceiver
119
131
return definitions.GettableApiReceiver {}, err
120
132
}
121
133
122
- provenances , err := rs .provisioningStore .GetProvenances (ctx , q .OrgID , "contactPoint" )
134
+ provenances , err := rs .provisioningStore .GetProvenances (ctx , q .OrgID , ( & definitions. EmbeddedContactPoint {}). ResourceType () )
123
135
if err != nil {
124
136
return definitions.GettableApiReceiver {}, err
125
137
}
@@ -158,7 +170,7 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
158
170
return nil , err
159
171
}
160
172
161
- provenances , err := rs .provisioningStore .GetProvenances (ctx , q .OrgID , "contactPoint" )
173
+ provenances , err := rs .provisioningStore .GetProvenances (ctx , q .OrgID , ( & definitions. EmbeddedContactPoint {}). ResourceType () )
162
174
if err != nil {
163
175
return nil , err
164
176
}
@@ -213,6 +225,83 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
213
225
return output , nil
214
226
}
215
227
228
+ // DeleteReceiver deletes a receiver by uid.
229
+ // UID field currently does not exist, we assume the uid is a particular hashed value of the receiver name.
230
+ func (rs * ReceiverService ) DeleteReceiver (ctx context.Context , uid string , orgID int64 , callerProvenance definitions.Provenance , version string ) error {
231
+ //TODO: Check delete permissions.
232
+ baseCfg , err := rs .cfgStore .GetLatestAlertmanagerConfiguration (ctx , orgID )
233
+ if err != nil {
234
+ return err
235
+ }
236
+
237
+ cfg := definitions.PostableUserConfig {}
238
+ err = json .Unmarshal ([]byte (baseCfg .AlertmanagerConfiguration ), & cfg )
239
+ if err != nil {
240
+ return err
241
+ }
242
+
243
+ idx , recv := getReceiverByUID (cfg , uid )
244
+ if recv == nil {
245
+ return ErrNotFound // TODO: nil?
246
+ }
247
+
248
+ // TODO: Implement + check optimistic concurrency.
249
+
250
+ storedProvenance , err := rs .getContactPointProvenance (ctx , recv , orgID )
251
+ if err != nil {
252
+ return err
253
+ }
254
+
255
+ if err := rs .validator (storedProvenance , models .Provenance (callerProvenance )); err != nil {
256
+ return err
257
+ }
258
+
259
+ if isReceiverInUse (recv .Name , []* definitions.Route {cfg .AlertmanagerConfig .Route }) {
260
+ return ErrReceiverInUse .Errorf ("" )
261
+ }
262
+
263
+ // Remove the receiver from the configuration.
264
+ cfg .AlertmanagerConfig .Receivers = append (cfg .AlertmanagerConfig .Receivers [:idx ], cfg .AlertmanagerConfig .Receivers [idx + 1 :]... )
265
+
266
+ return rs .xact .InTransaction (ctx , func (ctx context.Context ) error {
267
+ serialized , err := json .Marshal (cfg )
268
+ if err != nil {
269
+ return err
270
+ }
271
+ cmd := models.SaveAlertmanagerConfigurationCmd {
272
+ AlertmanagerConfiguration : string (serialized ),
273
+ ConfigurationVersion : baseCfg .ConfigurationVersion ,
274
+ FetchedConfigurationHash : baseCfg .ConfigurationHash ,
275
+ Default : false ,
276
+ OrgID : orgID ,
277
+ }
278
+
279
+ err = rs .cfgStore .UpdateAlertmanagerConfiguration (ctx , & cmd )
280
+ if err != nil {
281
+ return err
282
+ }
283
+
284
+ // Remove provenance for all integrations in the receiver.
285
+ for _ , integration := range recv .GrafanaManagedReceivers {
286
+ target := definitions.EmbeddedContactPoint {UID : integration .UID }
287
+ if err := rs .provisioningStore .DeleteProvenance (ctx , & target , orgID ); err != nil {
288
+ return err
289
+ }
290
+ }
291
+ return nil
292
+ })
293
+ }
294
+
295
+ func (rs * ReceiverService ) CreateReceiver (ctx context.Context , r definitions.GettableApiReceiver , orgID int64 ) (definitions.GettableApiReceiver , error ) {
296
+ // TODO: Stub
297
+ panic ("not implemented" )
298
+ }
299
+
300
+ func (rs * ReceiverService ) UpdateReceiver (ctx context.Context , r definitions.GettableApiReceiver , orgID int64 ) (definitions.GettableApiReceiver , error ) {
301
+ // TODO: Stub
302
+ panic ("not implemented" )
303
+ }
304
+
216
305
func (rs * ReceiverService ) decryptOrRedact (ctx context.Context , decrypt bool , name , fallback string ) func (value string ) string {
217
306
return func (value string ) string {
218
307
if ! decrypt {
@@ -232,3 +321,61 @@ func (rs *ReceiverService) decryptOrRedact(ctx context.Context, decrypt bool, na
232
321
return string (decrypted )
233
322
}
234
323
}
324
+
325
+ // getContactPointProvenance determines the provenance of a definitions.PostableApiReceiver based on the provenance of its integrations.
326
+ func (rs * ReceiverService ) getContactPointProvenance (ctx context.Context , r * definitions.PostableApiReceiver , orgID int64 ) (models.Provenance , error ) {
327
+ if len (r .GrafanaManagedReceivers ) == 0 {
328
+ return models .ProvenanceNone , nil
329
+ }
330
+
331
+ storedProvenances , err := rs .provisioningStore .GetProvenances (ctx , orgID , (& definitions.EmbeddedContactPoint {}).ResourceType ())
332
+ if err != nil {
333
+ return "" , err
334
+ }
335
+
336
+ // Current provisioning works on the integration level, so we need some way to determine the provenance of the
337
+ // entire receiver. All integrations in a receiver should have the same provenance, but we don't want to rely on
338
+ // this assumption in case the first provenance is None and a later one is not. To this end, we return the first
339
+ // non-zero provenance we find.
340
+ for _ , contactPoint := range r .GrafanaManagedReceivers {
341
+ if p , exists := storedProvenances [contactPoint .UID ]; exists && p != models .ProvenanceNone {
342
+ return p , nil
343
+ }
344
+ }
345
+ return models .ProvenanceNone , nil
346
+ }
347
+
348
+ // getReceiverByUID returns the index and receiver with the given UID.
349
+ func getReceiverByUID (cfg definitions.PostableUserConfig , uid string ) (int , * definitions.PostableApiReceiver ) {
350
+ for i , r := range cfg .AlertmanagerConfig .Receivers {
351
+ if getUID (r ) == uid {
352
+ return i , r
353
+ }
354
+ }
355
+ return 0 , nil
356
+ }
357
+
358
+ // getUID returns the UID of a PostableApiReceiver.
359
+ // Currently, the UID is a hash of the receiver name.
360
+ func getUID (t * definitions.PostableApiReceiver ) string { // TODO replace to stable UID when we switch to normal storage
361
+ sum := fnv .New64 ()
362
+ _ , _ = sum .Write ([]byte (t .Name ))
363
+ return fmt .Sprintf ("%016x" , sum .Sum64 ())
364
+ }
365
+
366
+ // TODO: Check if the contact point is used directly in an alert rule.
367
+ // isReceiverInUse checks if a receiver is used in a route or any of its sub-routes.
368
+ func isReceiverInUse (name string , routes []* definitions.Route ) bool {
369
+ if len (routes ) == 0 {
370
+ return false
371
+ }
372
+ for _ , route := range routes {
373
+ if route .Receiver == name {
374
+ return true
375
+ }
376
+ if isReceiverInUse (name , route .Routes ) {
377
+ return true
378
+ }
379
+ }
380
+ return false
381
+ }
0 commit comments