2022-04-13 22:15:55 +02:00
package provisioning
import (
"context"
"encoding/base64"
2023-09-08 15:09:35 -04:00
"errors"
2022-04-13 22:15:55 +02:00
"fmt"
"sort"
2023-07-20 14:35:56 -04:00
"strings"
2022-04-13 22:15:55 +02:00
2023-03-29 13:34:59 -04:00
alertingNotify "github.com/grafana/alerting/notify"
2022-12-16 13:01:06 -05:00
"github.com/prometheus/alertmanager/config"
2022-04-13 22:15:55 +02:00
"github.com/grafana/grafana/pkg/infra/log"
2023-11-14 15:47:34 +01:00
"github.com/grafana/grafana/pkg/services/auth/identity"
2022-04-13 22:15:55 +02:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
2024-02-01 14:42:59 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
2023-03-29 13:34:59 -04:00
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
2024-02-01 14:42:59 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/store"
2022-04-13 22:15:55 +02:00
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/util"
)
2024-02-15 09:45:10 -05:00
type AlertRuleNotificationSettingsStore interface {
RenameReceiverInNotificationSettings ( ctx context . Context , orgID int64 , oldReceiver , newReceiver string ) ( int , error )
ListNotificationSettings ( ctx context . Context , q models . ListNotificationSettingsQuery ) ( map [ models . AlertRuleKey ] [ ] models . NotificationSettings , error )
}
2022-04-13 22:15:55 +02:00
type ContactPointService struct {
2024-02-15 09:45:10 -05:00
configStore * alertmanagerConfigStoreImpl
encryptionService secrets . Service
provenanceStore ProvisioningStore
notificationSettingsStore AlertRuleNotificationSettingsStore
xact TransactionManager
receiverService receiverService
log log . Logger
2024-02-01 14:42:59 -05:00
}
type receiverService interface {
GetReceivers ( ctx context . Context , query models . GetReceiversQuery , user identity . Requester ) ( [ ] apimodels . GettableApiReceiver , error )
2022-04-13 22:15:55 +02:00
}
2022-06-09 03:38:46 -05:00
func NewContactPointService ( store AMConfigStore , encryptionService secrets . Service ,
2024-02-15 09:45:10 -05:00
provenanceStore ProvisioningStore , xact TransactionManager , receiverService receiverService , log log . Logger ,
nsStore AlertRuleNotificationSettingsStore ) * ContactPointService {
2022-04-13 22:15:55 +02:00
return & ContactPointService {
2024-01-05 16:15:18 -05:00
configStore : & alertmanagerConfigStoreImpl {
store : store ,
} ,
2024-02-15 09:45:10 -05:00
receiverService : receiverService ,
encryptionService : encryptionService ,
provenanceStore : provenanceStore ,
xact : xact ,
log : log ,
notificationSettingsStore : nsStore ,
2022-04-13 22:15:55 +02:00
}
}
2022-07-11 17:11:46 -05:00
type ContactPointQuery struct {
// Optionally filter by name.
Name string
OrgID int64
2023-07-20 14:35:56 -04:00
// Optionally decrypt secure settings, requires OrgAdmin.
Decrypt bool
2022-07-11 17:11:46 -05:00
}
2023-07-20 14:35:56 -04:00
// GetContactPoints returns contact points. If q.Decrypt is true and the user is an OrgAdmin, decrypted secure settings are included instead of redacted ones.
2023-11-14 15:47:34 +01:00
func ( ecp * ContactPointService ) GetContactPoints ( ctx context . Context , q ContactPointQuery , u identity . Requester ) ( [ ] apimodels . EmbeddedContactPoint , error ) {
2024-02-01 14:42:59 -05:00
receiverQuery := models . GetReceiversQuery {
OrgID : q . OrgID ,
Decrypt : q . Decrypt ,
2023-07-20 14:35:56 -04:00
}
2024-02-01 14:42:59 -05:00
if q . Name != "" {
receiverQuery . Names = [ ] string { q . Name }
2022-04-13 22:15:55 +02:00
}
2023-10-05 16:13:34 -04:00
2024-02-01 14:42:59 -05:00
res , err := ecp . receiverService . GetReceivers ( ctx , receiverQuery , u )
if err != nil {
return nil , convertRecSvcErr ( err )
}
grafanaReceivers := [ ] * apimodels . GettableGrafanaReceiver { }
2024-02-19 10:30:13 -05:00
if q . Name != "" && len ( res ) > 0 {
2024-02-01 14:42:59 -05:00
grafanaReceivers = res [ 0 ] . GettableGrafanaReceivers . GrafanaManagedReceivers // we only expect one receiver group
} else {
for _ , r := range res {
grafanaReceivers = append ( grafanaReceivers , r . GettableGrafanaReceivers . GrafanaManagedReceivers ... )
2022-07-11 17:11:46 -05:00
}
2024-02-01 14:42:59 -05:00
}
2022-07-11 17:11:46 -05:00
2024-02-01 14:42:59 -05:00
var contactPoints [ ] apimodels . EmbeddedContactPoint
for _ , gr := range grafanaReceivers {
contactPoint , err := GettableGrafanaReceiverToEmbeddedContactPoint ( gr )
2022-12-16 13:01:06 -05:00
if err != nil {
return nil , err
}
2024-02-01 14:42:59 -05:00
contactPoints = append ( contactPoints , contactPoint )
2022-04-13 22:15:55 +02:00
}
2024-02-01 14:42:59 -05:00
2022-04-13 22:15:55 +02:00
sort . SliceStable ( contactPoints , func ( i , j int ) bool {
2023-07-20 14:35:56 -04:00
switch strings . Compare ( contactPoints [ i ] . Name , contactPoints [ j ] . Name ) {
case - 1 :
return true
case 1 :
return false
}
return contactPoints [ i ] . UID < contactPoints [ j ] . UID
2022-04-13 22:15:55 +02:00
} )
2024-02-01 14:42:59 -05:00
2022-04-13 22:15:55 +02:00
return contactPoints , nil
}
2022-06-23 15:13:39 -05:00
// getContactPointDecrypted is an internal-only function that gets full contact point info, included encrypted fields.
// nil is returned if no matching contact point exists.
2022-04-13 22:15:55 +02:00
func ( ecp * ContactPointService ) getContactPointDecrypted ( ctx context . Context , orgID int64 , uid string ) ( apimodels . EmbeddedContactPoint , error ) {
2024-01-05 16:15:18 -05:00
revision , err := ecp . configStore . Get ( ctx , orgID )
2022-04-13 22:15:55 +02:00
if err != nil {
return apimodels . EmbeddedContactPoint { } , err
}
2022-05-23 18:16:03 -05:00
for _ , receiver := range revision . cfg . GetGrafanaReceiverMap ( ) {
2022-04-13 22:15:55 +02:00
if receiver . UID != uid {
continue
}
2023-10-05 16:13:34 -04:00
embeddedContactPoint , err := PostableGrafanaReceiverToEmbeddedContactPoint (
receiver ,
2024-01-05 16:15:18 -05:00
models . ProvenanceNone , // TODO should be correct provenance?
2023-10-05 16:13:34 -04:00
ecp . decryptValueOrRedacted ( true , receiver . UID ) ,
)
2022-12-16 13:01:06 -05:00
if err != nil {
return apimodels . EmbeddedContactPoint { } , err
}
2022-04-13 22:15:55 +02:00
return embeddedContactPoint , nil
}
2022-06-23 15:13:39 -05:00
return apimodels . EmbeddedContactPoint { } , fmt . Errorf ( "%w: contact point with uid '%s' not found" , ErrNotFound , uid )
2022-04-13 22:15:55 +02:00
}
func ( ecp * ContactPointService ) CreateContactPoint ( ctx context . Context , orgID int64 ,
contactPoint apimodels . EmbeddedContactPoint , provenance models . Provenance ) ( apimodels . EmbeddedContactPoint , error ) {
2023-04-25 13:39:46 -04:00
if err := ValidateContactPoint ( ctx , contactPoint , ecp . encryptionService . GetDecryptedValue ) ; err != nil {
2022-06-09 03:38:46 -05:00
return apimodels . EmbeddedContactPoint { } , fmt . Errorf ( "%w: %s" , ErrValidation , err . Error ( ) )
2022-04-13 22:15:55 +02:00
}
2024-01-05 16:15:18 -05:00
revision , err := ecp . configStore . Get ( ctx , orgID )
2022-04-13 22:15:55 +02:00
if err != nil {
return apimodels . EmbeddedContactPoint { } , err
}
2023-03-29 13:34:59 -04:00
extractedSecrets , err := RemoveSecretsForContactPoint ( & contactPoint )
2022-04-13 22:15:55 +02:00
if err != nil {
return apimodels . EmbeddedContactPoint { } , err
}
for k , v := range extractedSecrets {
encryptedValue , err := ecp . encryptValue ( v )
if err != nil {
return apimodels . EmbeddedContactPoint { } , err
}
extractedSecrets [ k ] = encryptedValue
}
2022-06-03 10:33:47 +02:00
if contactPoint . UID == "" {
contactPoint . UID = util . GenerateShortUID ( )
2023-09-08 15:09:35 -04:00
} else if err := util . ValidateUID ( contactPoint . UID ) ; err != nil {
return apimodels . EmbeddedContactPoint { } , errors . Join ( ErrValidation , fmt . Errorf ( "cannot create contact point with UID '%s': %w" , contactPoint . UID , err ) )
2022-06-03 10:33:47 +02:00
}
2022-12-16 13:01:06 -05:00
jsonData , err := contactPoint . Settings . MarshalJSON ( )
if err != nil {
return apimodels . EmbeddedContactPoint { } , err
}
2022-04-13 22:15:55 +02:00
grafanaReceiver := & apimodels . PostableGrafanaReceiver {
UID : contactPoint . UID ,
Name : contactPoint . Name ,
Type : contactPoint . Type ,
DisableResolveMessage : contactPoint . DisableResolveMessage ,
2022-12-16 13:01:06 -05:00
Settings : jsonData ,
2022-04-13 22:15:55 +02:00
SecureSettings : extractedSecrets ,
}
receiverFound := false
2022-05-23 18:16:03 -05:00
for _ , receiver := range revision . cfg . AlertmanagerConfig . Receivers {
2022-06-27 18:57:47 +02:00
// check if uid is already used in receiver
for _ , rec := range receiver . PostableGrafanaReceivers . GrafanaManagedReceivers {
if grafanaReceiver . UID == rec . UID {
return apimodels . EmbeddedContactPoint { } , fmt . Errorf (
"receiver configuration with UID '%s' already exist in contact point '%s'. Please use unique identifiers for receivers across all contact points" ,
rec . UID ,
rec . Name )
}
}
2022-04-13 22:15:55 +02:00
if receiver . Name == contactPoint . Name {
receiver . PostableGrafanaReceivers . GrafanaManagedReceivers = append ( receiver . PostableGrafanaReceivers . GrafanaManagedReceivers , grafanaReceiver )
receiverFound = true
}
}
if ! receiverFound {
2022-05-23 18:16:03 -05:00
revision . cfg . AlertmanagerConfig . Receivers = append ( revision . cfg . AlertmanagerConfig . Receivers , & apimodels . PostableApiReceiver {
2022-04-13 22:15:55 +02:00
Receiver : config . Receiver {
Name : grafanaReceiver . Name ,
} ,
PostableGrafanaReceivers : apimodels . PostableGrafanaReceivers {
GrafanaManagedReceivers : [ ] * apimodels . PostableGrafanaReceiver { grafanaReceiver } ,
} ,
} )
}
err = ecp . xact . InTransaction ( ctx , func ( ctx context . Context ) error {
2024-01-05 16:15:18 -05:00
if err := ecp . configStore . Save ( ctx , revision , orgID ) ; err != nil {
2022-04-13 22:15:55 +02:00
return err
}
2024-01-05 16:15:18 -05:00
return ecp . provenanceStore . SetProvenance ( ctx , & contactPoint , orgID , provenance )
2022-04-13 22:15:55 +02:00
} )
if err != nil {
return apimodels . EmbeddedContactPoint { } , err
}
for k := range extractedSecrets {
contactPoint . Settings . Set ( k , apimodels . RedactedValue )
}
return contactPoint , nil
}
func ( ecp * ContactPointService ) UpdateContactPoint ( ctx context . Context , orgID int64 , contactPoint apimodels . EmbeddedContactPoint , provenance models . Provenance ) error {
// set all redacted values with the latest known value from the store
2022-06-09 03:38:46 -05:00
if contactPoint . Settings == nil {
return fmt . Errorf ( "%w: %s" , ErrValidation , "settings should not be empty" )
}
2022-04-13 22:15:55 +02:00
rawContactPoint , err := ecp . getContactPointDecrypted ( ctx , orgID , contactPoint . UID )
if err != nil {
return err
}
2023-10-12 13:43:10 +01:00
secretKeys , err := channels_config . GetSecretKeysForContactPointType ( contactPoint . Type )
2022-04-13 22:15:55 +02:00
if err != nil {
2022-06-09 03:38:46 -05:00
return fmt . Errorf ( "%w: %s" , ErrValidation , err . Error ( ) )
2022-04-13 22:15:55 +02:00
}
for _ , secretKey := range secretKeys {
secretValue := contactPoint . Settings . Get ( secretKey ) . MustString ( )
if secretValue == apimodels . RedactedValue {
contactPoint . Settings . Set ( secretKey , rawContactPoint . Settings . Get ( secretKey ) . MustString ( ) )
}
}
2022-06-09 03:38:46 -05:00
2022-04-13 22:15:55 +02:00
// validate merged values
2023-04-25 13:39:46 -04:00
if err := ValidateContactPoint ( ctx , contactPoint , ecp . encryptionService . GetDecryptedValue ) ; err != nil {
2022-06-09 03:38:46 -05:00
return fmt . Errorf ( "%w: %s" , ErrValidation , err . Error ( ) )
2022-04-13 22:15:55 +02:00
}
2022-06-09 03:38:46 -05:00
2023-04-18 15:10:36 +02:00
// check that provenance is not changed in an invalid way
2022-04-26 10:30:57 -05:00
storedProvenance , err := ecp . provenanceStore . GetProvenance ( ctx , & contactPoint , orgID )
2022-04-13 22:15:55 +02:00
if err != nil {
return err
}
if storedProvenance != provenance && storedProvenance != models . ProvenanceNone {
2023-04-18 15:10:36 +02:00
return fmt . Errorf ( "cannot change provenance from '%s' to '%s'" , storedProvenance , provenance )
2022-04-13 22:15:55 +02:00
}
// transform to internal model
2023-03-29 13:34:59 -04:00
extractedSecrets , err := RemoveSecretsForContactPoint ( & contactPoint )
2022-04-13 22:15:55 +02:00
if err != nil {
return err
}
for k , v := range extractedSecrets {
encryptedValue , err := ecp . encryptValue ( v )
if err != nil {
return err
}
extractedSecrets [ k ] = encryptedValue
}
2022-12-16 13:01:06 -05:00
jsonData , err := contactPoint . Settings . MarshalJSON ( )
if err != nil {
return err
}
2022-04-13 22:15:55 +02:00
mergedReceiver := & apimodels . PostableGrafanaReceiver {
UID : contactPoint . UID ,
Name : contactPoint . Name ,
Type : contactPoint . Type ,
DisableResolveMessage : contactPoint . DisableResolveMessage ,
2022-12-16 13:01:06 -05:00
Settings : jsonData ,
2022-04-13 22:15:55 +02:00
SecureSettings : extractedSecrets ,
}
// save to store
2024-01-05 16:15:18 -05:00
revision , err := ecp . configStore . Get ( ctx , orgID )
2022-04-13 22:15:55 +02:00
if err != nil {
return err
}
2022-06-17 10:19:22 -05:00
2024-02-15 09:45:10 -05:00
configModified , renamedReceiver := stitchReceiver ( revision . cfg , mergedReceiver )
2022-06-17 10:19:22 -05:00
if ! configModified {
return fmt . Errorf ( "contact point with uid '%s' not found" , mergedReceiver . UID )
2022-04-13 22:15:55 +02:00
}
2022-06-17 10:19:22 -05:00
2024-01-05 16:15:18 -05:00
err = ecp . xact . InTransaction ( ctx , func ( ctx context . Context ) error {
if err := ecp . configStore . Save ( ctx , revision , orgID ) ; err != nil {
2022-04-13 22:15:55 +02:00
return err
}
2024-02-15 09:45:10 -05:00
if renamedReceiver != "" && renamedReceiver != mergedReceiver . Name {
affected , err := ecp . notificationSettingsStore . RenameReceiverInNotificationSettings ( ctx , orgID , renamedReceiver , mergedReceiver . Name )
if err != nil {
return err
}
if affected > 0 {
ecp . log . Info ( "Renamed receiver in notification settings" , "oldName" , renamedReceiver , "newName" , mergedReceiver . Name , "affectedSettings" , affected )
}
}
2024-01-05 16:15:18 -05:00
return ecp . provenanceStore . SetProvenance ( ctx , & contactPoint , orgID , provenance )
2022-04-13 22:15:55 +02:00
} )
2024-01-05 16:15:18 -05:00
if err != nil {
return err
}
return nil
2022-04-13 22:15:55 +02:00
}
func ( ecp * ContactPointService ) DeleteContactPoint ( ctx context . Context , orgID int64 , uid string ) error {
2024-01-05 16:15:18 -05:00
revision , err := ecp . configStore . Get ( ctx , orgID )
2022-04-13 22:15:55 +02:00
if err != nil {
return err
}
// Indicates if the full contact point is removed or just one of the
// configurations, as a contactpoint can consist of any number of
// configurations.
fullRemoval := false
// Name of the contact point that will be removed, might be used if a
// full removal is done to check if it's referenced in any route.
name := ""
2022-05-23 18:16:03 -05:00
for i , receiver := range revision . cfg . AlertmanagerConfig . Receivers {
2022-04-13 22:15:55 +02:00
for j , grafanaReceiver := range receiver . GrafanaManagedReceivers {
if grafanaReceiver . UID == uid {
name = grafanaReceiver . Name
receiver . GrafanaManagedReceivers = append ( receiver . GrafanaManagedReceivers [ : j ] , receiver . GrafanaManagedReceivers [ j + 1 : ] ... )
// if this was the last receiver we removed, we remove the whole receiver
if len ( receiver . GrafanaManagedReceivers ) == 0 {
fullRemoval = true
2022-05-23 18:16:03 -05:00
revision . cfg . AlertmanagerConfig . Receivers = append ( revision . cfg . AlertmanagerConfig . Receivers [ : i ] , revision . cfg . AlertmanagerConfig . Receivers [ i + 1 : ] ... )
2022-04-13 22:15:55 +02:00
}
break
}
}
}
2022-05-23 18:16:03 -05:00
if fullRemoval && isContactPointInUse ( name , [ ] * apimodels . Route { revision . cfg . AlertmanagerConfig . Route } ) {
2022-04-13 22:15:55 +02:00
return fmt . Errorf ( "contact point '%s' is currently used by a notification policy" , name )
}
2024-01-05 16:15:18 -05:00
2022-04-13 22:15:55 +02:00
return ecp . xact . InTransaction ( ctx , func ( ctx context . Context ) error {
2024-02-15 09:45:10 -05:00
if fullRemoval {
used , err := ecp . notificationSettingsStore . ListNotificationSettings ( ctx , models . ListNotificationSettingsQuery { OrgID : orgID , ReceiverName : name } )
if err != nil {
return fmt . Errorf ( "failed to query alert rules for reference to the contact point '%s': %w" , name , err )
}
if len ( used ) > 0 {
uids := make ( [ ] string , 0 , len ( used ) )
for key := range used {
uids = append ( uids , key . UID )
}
ecp . log . Error ( "Cannot delete contact point because it is used in rule's notification settings" , "receiverName" , name , "rulesUid" , strings . Join ( uids , "," ) )
return fmt . Errorf ( "contact point '%s' is currently used in notification settings by one or many alert rules" , name )
}
}
2024-01-05 16:15:18 -05:00
if err := ecp . configStore . Save ( ctx , revision , orgID ) ; err != nil {
return err
}
2022-04-26 10:30:57 -05:00
target := & apimodels . EmbeddedContactPoint {
UID : uid ,
}
2024-01-05 16:15:18 -05:00
return ecp . provenanceStore . DeleteProvenance ( ctx , target , orgID )
2022-04-13 22:15:55 +02:00
} )
}
func isContactPointInUse ( name string , routes [ ] * apimodels . Route ) bool {
if len ( routes ) == 0 {
return false
}
for _ , route := range routes {
if route . Receiver == name {
return true
}
if isContactPointInUse ( name , route . Routes ) {
return true
}
}
return false
}
2023-10-05 16:13:34 -04:00
// decryptValueOrRedacted returns a function that decodes a string from Base64 and then decrypts using secrets.Service.
// If argument 'decrypt' is false, then returns definitions.RedactedValue regardless of the decrypted value.
// Otherwise, it returns the decoded and decrypted value. The function returns empty string in the case of errors, which are logged
func ( ecp * ContactPointService ) decryptValueOrRedacted ( decrypt bool , integrationUID string ) func ( v string ) string {
return func ( value string ) string {
decodeValue , err := base64 . StdEncoding . DecodeString ( value )
if err != nil {
ecp . log . Warn ( "Failed to decode secret value from Base64" , "error" , err . Error ( ) , "integrationUid" , integrationUID )
return ""
}
decryptedValue , err := ecp . encryptionService . Decrypt ( context . Background ( ) , decodeValue )
if err != nil {
ecp . log . Warn ( "Failed to decrypt secret value" , "error" , err . Error ( ) , "integrationUid" , integrationUID )
return ""
}
if decrypt {
return string ( decryptedValue )
} else {
return apimodels . RedactedValue
}
2022-04-13 22:15:55 +02:00
}
}
func ( ecp * ContactPointService ) encryptValue ( value string ) ( string , error ) {
encryptedData , err := ecp . encryptionService . Encrypt ( context . Background ( ) , [ ] byte ( value ) , secrets . WithoutScope ( ) )
if err != nil {
return "" , fmt . Errorf ( "failed to encrypt secure settings: %w" , err )
}
return base64 . StdEncoding . EncodeToString ( encryptedData ) , nil
}
2022-06-17 10:19:22 -05:00
2024-01-05 16:15:18 -05:00
// stitchReceiver modifies a receiver, target, in an alertmanager configStore. It modifies the given configStore in-place.
// Returns true if the configStore was altered in any way, and false otherwise.
2024-02-15 09:45:10 -05:00
// If integration was moved to another group and it was the last in the previous group, the second parameter contains the name of the old group that is gone
func stitchReceiver ( cfg * apimodels . PostableUserConfig , target * apimodels . PostableGrafanaReceiver ) ( bool , string ) {
2022-06-17 10:19:22 -05:00
// Algorithm to fix up receivers. Receivers are very complex and depend heavily on internal consistency.
// All receivers in a given receiver group have the same name. We must maintain this across renames.
configModified := false
2024-02-15 09:45:10 -05:00
renamedReceiver := ""
2022-06-17 10:19:22 -05:00
groupLoop :
2023-09-04 13:30:15 -04:00
for groupIdx , receiverGroup := range cfg . AlertmanagerConfig . Receivers {
2022-06-17 10:19:22 -05:00
// Does the current group contain the grafana receiver we're interested in?
for i , grafanaReceiver := range receiverGroup . GrafanaManagedReceivers {
if grafanaReceiver . UID == target . UID {
// If it's a basic field change, simply replace it. Done!
//
// NOTE:
// In a "normal" database, receiverGroup.Name should always == grafanaReceiver.Name.
// Check it regardless.
// If these values are out of sync due to some bug elsewhere in the code, let's fix it up.
// Our receiver group fixing logic below will handle it.
if grafanaReceiver . Name == target . Name && receiverGroup . Name == grafanaReceiver . Name {
receiverGroup . GrafanaManagedReceivers [ i ] = target
configModified = true
break groupLoop
}
// If we're renaming, we'll need to fix up the macro receiver group for consistency.
// Firstly, if we're the only receiver in the group, simply rename the group to match. Done!
if len ( receiverGroup . GrafanaManagedReceivers ) == 1 {
2022-09-08 15:20:52 +02:00
replaceReferences ( receiverGroup . Name , target . Name , cfg . AlertmanagerConfig . Route )
2022-06-17 10:19:22 -05:00
receiverGroup . Name = target . Name
receiverGroup . GrafanaManagedReceivers [ i ] = target
2024-02-15 09:45:10 -05:00
renamedReceiver = receiverGroup . Name
2022-06-17 10:19:22 -05:00
}
// Otherwise, we only want to rename the receiver we are touching... NOT all of them.
// Check to see whether a different group with the name we want already exists.
2023-04-24 20:23:23 -05:00
for _ , candidateExistingGroup := range cfg . AlertmanagerConfig . Receivers {
2022-06-17 10:19:22 -05:00
// If so, put our modified receiver into that group. Done!
if candidateExistingGroup . Name == target . Name {
// Drop it from the old group...
receiverGroup . GrafanaManagedReceivers = append ( receiverGroup . GrafanaManagedReceivers [ : i ] , receiverGroup . GrafanaManagedReceivers [ i + 1 : ] ... )
// Add the modified receiver to the new group...
candidateExistingGroup . GrafanaManagedReceivers = append ( candidateExistingGroup . GrafanaManagedReceivers , target )
configModified = true
2023-09-04 13:30:15 -04:00
// if the old receiver group turns out to be empty. Remove it.
if len ( receiverGroup . GrafanaManagedReceivers ) == 0 {
cfg . AlertmanagerConfig . Receivers = append ( cfg . AlertmanagerConfig . Receivers [ : groupIdx ] , cfg . AlertmanagerConfig . Receivers [ groupIdx + 1 : ] ... )
}
2022-06-17 10:19:22 -05:00
break groupLoop
}
}
// Doesn't exist? Create a new group just for the receiver.
newGroup := & apimodels . PostableApiReceiver {
Receiver : config . Receiver {
Name : target . Name ,
} ,
PostableGrafanaReceivers : apimodels . PostableGrafanaReceivers {
GrafanaManagedReceivers : [ ] * apimodels . PostableGrafanaReceiver {
target ,
} ,
} ,
}
cfg . AlertmanagerConfig . Receivers = append ( cfg . AlertmanagerConfig . Receivers , newGroup )
// Drop it from the old spot.
receiverGroup . GrafanaManagedReceivers = append ( receiverGroup . GrafanaManagedReceivers [ : i ] , receiverGroup . GrafanaManagedReceivers [ i + 1 : ] ... )
configModified = true
break groupLoop
}
}
}
2024-02-15 09:45:10 -05:00
return configModified , renamedReceiver
2022-06-17 10:19:22 -05:00
}
2022-09-08 15:20:52 +02:00
func replaceReferences ( oldName , newName string , routes ... * apimodels . Route ) {
if len ( routes ) == 0 {
return
}
for _ , route := range routes {
if route . Receiver == oldName {
route . Receiver = newName
}
replaceReferences ( oldName , newName , route . Routes ... )
}
}
2023-03-29 13:34:59 -04:00
2023-04-25 13:39:46 -04:00
func ValidateContactPoint ( ctx context . Context , e apimodels . EmbeddedContactPoint , decryptFunc alertingNotify . GetDecryptedValueFn ) error {
2023-03-29 13:34:59 -04:00
if e . Type == "" {
return fmt . Errorf ( "type should not be an empty string" )
}
if e . Settings == nil {
return fmt . Errorf ( "settings should not be empty" )
}
2023-04-25 13:39:46 -04:00
integration , err := EmbeddedContactPointToGrafanaIntegrationConfig ( e )
2023-03-29 13:34:59 -04:00
if err != nil {
return err
}
2023-04-25 13:39:46 -04:00
_ , err = alertingNotify . BuildReceiverConfiguration ( ctx , & alertingNotify . APIReceiver {
GrafanaIntegrations : alertingNotify . GrafanaIntegrations {
Integrations : [ ] * alertingNotify . GrafanaIntegrationConfig { & integration } ,
} ,
} , decryptFunc )
if err != nil {
2023-03-29 13:34:59 -04:00
return err
}
return nil
}
// RemoveSecretsForContactPoint removes all secrets from the contact point's settings and returns them as a map. Returns error if contact point type is not known.
func RemoveSecretsForContactPoint ( e * apimodels . EmbeddedContactPoint ) ( map [ string ] string , error ) {
s := map [ string ] string { }
2023-10-12 13:43:10 +01:00
secretKeys , err := channels_config . GetSecretKeysForContactPointType ( e . Type )
2023-03-29 13:34:59 -04:00
if err != nil {
return nil , err
}
for _ , secretKey := range secretKeys {
secretValue := e . Settings . Get ( secretKey ) . MustString ( )
e . Settings . Del ( secretKey )
s [ secretKey ] = secretValue
}
return s , nil
}
2024-02-01 14:42:59 -05:00
// handleWrappedError unwraps an error and wraps it with a new expected error type. If the error is not wrapped, it returns just the expected error.
func handleWrappedError ( err error , expected error ) error {
err = errors . Unwrap ( err )
if err == nil {
return expected
}
return fmt . Errorf ( "%w: %s" , expected , err . Error ( ) )
}
// convertRecSvcErr converts errors from notifier.ReceiverService to errors expected from ContactPointService.
func convertRecSvcErr ( err error ) error {
if errors . Is ( err , notifier . ErrPermissionDenied ) {
return handleWrappedError ( err , ErrPermissionDenied )
}
if errors . Is ( err , store . ErrNoAlertmanagerConfiguration ) {
return ErrNoAlertmanagerConfiguration . Errorf ( "" )
}
return err
}