Alerting: Migrate to integration schema (#111643)

* update tests to assert against snapshot
* remove channel_config package replaced by schemas from alerting module
* update  references to use new schema
This commit is contained in:
Yuri Tseretyan 2025-09-26 09:31:50 -04:00 committed by GitHub
parent a8bff45256
commit b8f23eacd4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 4475 additions and 4255 deletions

View File

@ -204,7 +204,7 @@ require (
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/grafana/alerting v0.0.0-20250925193206-bd061d3d9185 // indirect
github.com/grafana/alerting v0.0.0-20250925200825-7a889aa4934d // indirect
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 // indirect
github.com/grafana/dataplane/sdata v0.0.9 // indirect
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect

View File

@ -721,8 +721,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grafana/alerting v0.0.0-20250925193206-bd061d3d9185 h1:R494uXJOz7glN76hJXKjbwu+VBYFsT0CFprsXmdHla0=
github.com/grafana/alerting v0.0.0-20250925193206-bd061d3d9185/go.mod h1:T5sitas9VhVj8/S9LeRLy6H75kTBdh/sCCqHo7gaQI8=
github.com/grafana/alerting v0.0.0-20250925200825-7a889aa4934d h1:zzEty7HgfXbQ/RiBCJFMqaZiJlqiXuz/Zbc6/H6ksuM=
github.com/grafana/alerting v0.0.0-20250925200825-7a889aa4934d/go.mod h1:T5sitas9VhVj8/S9LeRLy6H75kTBdh/sCCqHo7gaQI8=
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=

2
go.mod
View File

@ -86,7 +86,7 @@ require (
github.com/googleapis/gax-go/v2 v2.14.2 // @grafana/grafana-backend-group
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // @grafana/grafana-app-platform-squad
github.com/grafana/alerting v0.0.0-20250925193206-bd061d3d9185 // @grafana/alerting-backend
github.com/grafana/alerting v0.0.0-20250925200825-7a889aa4934d // @grafana/alerting-backend
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c // @grafana/identity-access-team
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 // @grafana/identity-access-team
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics

4
go.sum
View File

@ -1585,8 +1585,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grafana/alerting v0.0.0-20250925193206-bd061d3d9185 h1:R494uXJOz7glN76hJXKjbwu+VBYFsT0CFprsXmdHla0=
github.com/grafana/alerting v0.0.0-20250925193206-bd061d3d9185/go.mod h1:T5sitas9VhVj8/S9LeRLy6H75kTBdh/sCCqHo7gaQI8=
github.com/grafana/alerting v0.0.0-20250925200825-7a889aa4934d h1:zzEty7HgfXbQ/RiBCJFMqaZiJlqiXuz/Zbc6/H6ksuM=
github.com/grafana/alerting v0.0.0-20250925200825-7a889aa4934d/go.mod h1:T5sitas9VhVj8/S9LeRLy6H75kTBdh/sCCqHo7gaQI8=
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=

View File

@ -5,19 +5,48 @@ import (
"slices"
"strings"
"github.com/grafana/alerting/notify"
"github.com/grafana/alerting/receivers/schema"
"github.com/grafana/grafana/pkg/api/response"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
)
func (hs *HTTPServer) GetAlertNotifiers() func(*contextmodel.ReqContext) response.Response {
return func(r *contextmodel.ReqContext) response.Response {
if r.Query("version") == "2" {
v2 := slices.SortedFunc(channels_config.GetAvailableNotifiersV2(), func(a, b *channels_config.VersionedNotifierPlugin) int {
return strings.Compare(a.Type, b.Type)
v2 := notify.GetSchemaForAllIntegrations()
slices.SortFunc(v2, func(a, b schema.IntegrationTypeSchema) int {
return strings.Compare(string(a.Type), string(b.Type))
})
if r.Query("version") == "2" {
return response.JSON(http.StatusOK, v2)
}
return response.JSON(http.StatusOK, channels_config.GetAvailableNotifiers())
type NotifierPlugin struct {
Type string `json:"type"`
TypeAlias string `json:"typeAlias,omitempty"`
Name string `json:"name"`
Heading string `json:"heading"`
Description string `json:"description"`
Info string `json:"info"`
Options []schema.Field `json:"options"`
}
result := make([]*NotifierPlugin, 0, len(v2))
for _, s := range v2 {
v1, ok := s.GetVersion(schema.V1)
if !ok {
continue
}
result = append(result, &NotifierPlugin{
Type: string(s.Type),
Name: s.Name,
Description: s.Description,
Heading: s.Heading,
Info: s.Info,
Options: v1.Options,
})
}
return response.JSON(http.StatusOK, result)
}
}

View File

@ -15,6 +15,7 @@ import (
"time"
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/alerting/receivers/schema"
prometheus "github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/alertmanager/timeinterval"
@ -2037,7 +2038,7 @@ func TestApiContactPointExportSnapshot(t *testing.T) {
integration := models.IntegrationGen(
models.IntegrationMuts.WithName(allIntegrationsName),
models.IntegrationMuts.WithUID(fmt.Sprintf("%s-uid", integrationType)),
models.IntegrationMuts.WithValidConfig(integrationType),
models.IntegrationMuts.WithValidConfig(schema.IntegrationType(integrationType)),
)()
integration.DisableResolveMessage = redacted
allIntegrations = append(allIntegrations, integration)

View File

@ -13,8 +13,7 @@ import (
"strings"
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
"github.com/grafana/alerting/receivers/schema"
)
// GetReceiverQuery represents a query for a single receiver.
@ -228,30 +227,35 @@ func (f IntegrationFieldPath) With(segment string) IntegrationFieldPath {
// IntegrationConfig - The integration configuration
// error - Error if integration type not found or invalid version specified
func IntegrationConfigFromType(integrationType string, version *string) (IntegrationConfig, error) {
versionConfig, err := channels_config.ConfigForIntegrationType(integrationType)
if err != nil {
return IntegrationConfig{}, err
typeSchema, ok := alertingNotify.GetSchemaForIntegration(schema.IntegrationType(integrationType))
if !ok {
return IntegrationConfig{}, fmt.Errorf("integration type %s not found", integrationType)
}
// if particular version is requested and the version returned does not match, try to get the correct version
if version != nil && *version != string(versionConfig.Version) {
exists := false
versionConfig, exists = versionConfig.Plugin.GetVersion(channels_config.NotifierVersion(*version))
if !exists {
return IntegrationConfig{}, fmt.Errorf("version %s not found in config", *version)
if version == nil {
return IntegrationConfigFromSchema(typeSchema, typeSchema.CurrentVersion)
}
return IntegrationConfigFromSchema(typeSchema, schema.Version(*version))
}
// IntegrationConfigFromSchema returns an integration configuration for a given version of the integration type schema.
// Returns an error if the schema does not have such version
func IntegrationConfigFromSchema(typeSchema schema.IntegrationTypeSchema, version schema.Version) (IntegrationConfig, error) {
typeVersion, ok := typeSchema.GetVersion(version)
if !ok {
return IntegrationConfig{}, fmt.Errorf("version %s not found in config", version)
}
integrationConfig := IntegrationConfig{
Type: versionConfig.Plugin.Type,
Version: string(versionConfig.Version),
Fields: make(map[string]IntegrationField, len(versionConfig.Options)),
Type: string(typeSchema.Type),
Version: string(typeVersion.Version),
Fields: make(map[string]IntegrationField, len(typeVersion.Options)),
}
for _, option := range versionConfig.Options {
for _, option := range typeVersion.Options {
integrationConfig.Fields[option.PropertyName] = notifierOptionToIntegrationField(option)
}
return integrationConfig, nil
}
func notifierOptionToIntegrationField(option channels_config.NotifierOption) IntegrationField {
func notifierOptionToIntegrationField(option schema.Field) IntegrationField {
f := IntegrationField{
Name: option.PropertyName,
Secure: option.Secure,

View File

@ -6,11 +6,10 @@ import (
"testing"
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/alerting/receivers/schema"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/testutil"
)
@ -41,14 +40,14 @@ func TestReceiver_EncryptDecrypt(t *testing.T) {
encryptFn := Base64Enrypt
decryptnFn := Base64Decrypt
// Test that all known integration types encrypt and decrypt their secrets.
for integrationType := range alertingNotify.AllKnownConfigsForTesting {
t.Run(integrationType, func(t *testing.T) {
for it := range alertingNotify.AllKnownConfigsForTesting {
integrationType := schema.IntegrationType(it)
t.Run(string(integrationType), func(t *testing.T) {
decrypedIntegration := IntegrationGen(IntegrationMuts.WithValidConfig(integrationType))()
encrypted := decrypedIntegration.Clone()
secrets, err := channels_config.GetSecretKeysForContactPointType(integrationType, channels_config.V1)
assert.NoError(t, err)
for _, key := range secrets {
typeVersion, ok := alertingNotify.GetSchemaVersionForIntegration(integrationType, schema.V1)
require.True(t, ok)
for _, key := range typeVersion.GetSecretFieldsPaths() {
val, ok, err := extractField(encrypted.Settings, NewIntegrationFieldPath(key))
assert.NoError(t, err)
if ok {
@ -59,7 +58,7 @@ func TestReceiver_EncryptDecrypt(t *testing.T) {
}
testIntegration := decrypedIntegration.Clone()
err = testIntegration.Encrypt(encryptFn)
err := testIntegration.Encrypt(encryptFn)
assert.NoError(t, err)
require.Equal(t, encrypted, testIntegration)
@ -77,14 +76,15 @@ func TestIntegration_Redact(t *testing.T) {
return "TESTREDACTED"
}
// Test that all known integration types redact their secrets.
for integrationType := range alertingNotify.AllKnownConfigsForTesting {
t.Run(integrationType, func(t *testing.T) {
for it := range alertingNotify.AllKnownConfigsForTesting {
integrationType := schema.IntegrationType(it)
t.Run(string(integrationType), func(t *testing.T) {
validIntegration := IntegrationGen(IntegrationMuts.WithValidConfig(integrationType))()
expected := validIntegration.Clone()
secrets, err := channels_config.GetSecretKeysForContactPointType(integrationType, channels_config.V1)
assert.NoError(t, err)
for _, key := range secrets {
version, ok := alertingNotify.GetSchemaVersionForIntegration(integrationType, schema.V1)
require.True(t, ok)
for _, key := range version.GetSecretFieldsPaths() {
err := setField(expected.Settings, NewIntegrationFieldPath(key), func(current any) any {
if s, isString := current.(string); isString && s != "" {
delete(expected.SecureSettings, key)
@ -106,8 +106,9 @@ func TestIntegration_Validate(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
// Test that all known integration types are valid.
for integrationType := range alertingNotify.AllKnownConfigsForTesting {
t.Run(integrationType, func(t *testing.T) {
for it := range alertingNotify.AllKnownConfigsForTesting {
integrationType := schema.IntegrationType(it)
t.Run(string(integrationType), func(t *testing.T) {
validIntegration := IntegrationGen(IntegrationMuts.WithValidConfig(integrationType))()
assert.NoError(t, validIntegration.Encrypt(Base64Enrypt))
assert.NoErrorf(t, validIntegration.Validate(Base64Decrypt), "integration should be valid")
@ -241,19 +242,19 @@ func TestIntegration_WithExistingSecureFields(t *testing.T) {
func TestSecretsIntegrationConfig(t *testing.T) {
// Test that all known integration types have a config and correctly mark their secrets as secure.
for integrationType := range alertingNotify.AllKnownConfigsForTesting {
t.Run(integrationType, func(t *testing.T) {
config, err := IntegrationConfigFromType(integrationType, nil)
for it := range alertingNotify.AllKnownConfigsForTesting {
integrationType := schema.IntegrationType(it)
t.Run(string(integrationType), func(t *testing.T) {
schemaType, ok := alertingNotify.GetSchemaForIntegration(integrationType)
require.True(t, ok)
config, err := IntegrationConfigFromSchema(schemaType, schema.V1)
assert.NoError(t, err)
t.Run("v1 is current", func(t *testing.T) {
configv1, err := IntegrationConfigFromType(integrationType, util.Pointer(string(channels_config.V1)))
assert.NoError(t, err)
assert.Equal(t, config, configv1)
})
version, ok := schemaType.GetVersion(schema.V1)
require.True(t, ok)
secrets, err := channels_config.GetSecretKeysForContactPointType(integrationType, channels_config.V1)
assert.NoError(t, err)
secrets := version.GetSecretFieldsPaths()
allSecrets := make(map[string]struct{}, len(secrets))
for _, key := range secrets {
allSecrets[key] = struct{}{}
@ -270,17 +271,12 @@ func TestSecretsIntegrationConfig(t *testing.T) {
})
}
t.Run("Unknown type returns error", func(t *testing.T) {
_, err := IntegrationConfigFromType("__--**unknown_type**--__", nil)
assert.Error(t, err)
})
t.Run("Unknown version returns error", func(t *testing.T) {
version := util.Pointer("__--**unknown_version**--__")
types := maps.Keys(alertingNotify.AllKnownConfigsForTesting)
for itype := range types {
_, err := IntegrationConfigFromType(itype, version)
assert.Errorf(t, err, "unknown version for integration type %s did not return error but should", itype)
for s := range maps.Keys(alertingNotify.AllKnownConfigsForTesting) {
schemaType, _ := alertingNotify.GetSchemaForIntegration(schema.IntegrationType(s))
_, err := IntegrationConfigFromSchema(schemaType, "unknown")
require.Error(t, err)
return
}
})
}
@ -289,8 +285,9 @@ func TestIntegration_SecureFields(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
// Test that all known integration types have a config and correctly mark their secrets as secure.
for integrationType := range alertingNotify.AllKnownConfigsForTesting {
t.Run(integrationType, func(t *testing.T) {
for it := range alertingNotify.AllKnownConfigsForTesting {
integrationType := schema.IntegrationType(it)
t.Run(string(integrationType), func(t *testing.T) {
t.Run("contains SecureSettings", func(t *testing.T) {
validIntegration := IntegrationGen(IntegrationMuts.WithValidConfig(integrationType))()
expected := make(map[string]bool, len(validIntegration.SecureSettings))

View File

@ -13,6 +13,8 @@ import (
"github.com/go-openapi/strfmt"
"github.com/google/uuid"
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/alerting/receivers/schema"
"github.com/grafana/alerting/receivers/webex"
"github.com/grafana/grafana-plugin-sdk-go/data"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/pkg/labels"
@ -1209,7 +1211,7 @@ func (n ReceiverMutators) WithProvenance(provenance Provenance) Mutator[Receiver
}
}
func (n ReceiverMutators) WithValidIntegration(integrationType string) Mutator[Receiver] {
func (n ReceiverMutators) WithValidIntegration(integrationType schema.IntegrationType) Mutator[Receiver] {
return func(r *Receiver) {
// TODO add support for v0
integration := IntegrationGen(IntegrationMuts.WithValidConfig(integrationType))()
@ -1217,7 +1219,7 @@ func (n ReceiverMutators) WithValidIntegration(integrationType string) Mutator[R
}
}
func (n ReceiverMutators) WithInvalidIntegration(integrationType string) Mutator[Receiver] {
func (n ReceiverMutators) WithInvalidIntegration(integrationType schema.IntegrationType) Mutator[Receiver] {
return func(r *Receiver) {
// TODO add support for v0
integration := IntegrationGen(IntegrationMuts.WithInvalidConfig(integrationType))()
@ -1278,7 +1280,7 @@ func IntegrationGen(mutators ...Mutator[Integration]) func() Integration {
SecureSettings: make(map[string]string),
}
IntegrationMuts.WithValidConfig(randomIntegrationType)(&c)
IntegrationMuts.WithValidConfig(schema.IntegrationType(randomIntegrationType))(&c)
for _, mutator := range mutators {
mutator(&c)
@ -1312,11 +1314,12 @@ func (n IntegrationMutators) WithName(name string) Mutator[Integration] {
}
}
func (n IntegrationMutators) WithValidConfig(integrationType string) Mutator[Integration] {
func (n IntegrationMutators) WithValidConfig(integrationType schema.IntegrationType) Mutator[Integration] {
return func(c *Integration) {
// TODO add support for v0 integrations
config := alertingNotify.AllKnownConfigsForTesting[integrationType].GetRawNotifierConfig(c.Name)
integrationConfig, _ := IntegrationConfigFromType(integrationType, nil)
config := alertingNotify.AllKnownConfigsForTesting[string(integrationType)].GetRawNotifierConfig(c.Name)
typeSchema, _ := alertingNotify.GetSchemaForIntegration(integrationType)
integrationConfig, _ := IntegrationConfigFromSchema(typeSchema, schema.V1)
c.Config = integrationConfig
var settings map[string]any
@ -1332,13 +1335,13 @@ func (n IntegrationMutators) WithValidConfig(integrationType string) Mutator[Int
}
}
func (n IntegrationMutators) WithInvalidConfig(integrationType string) Mutator[Integration] {
func (n IntegrationMutators) WithInvalidConfig(integrationType schema.IntegrationType) Mutator[Integration] {
return func(c *Integration) {
integrationConfig, _ := IntegrationConfigFromType(integrationType, nil)
c.Config = integrationConfig
typeSchema, _ := alertingNotify.GetSchemaForIntegration(integrationType)
c.Config, _ = IntegrationConfigFromSchema(typeSchema, schema.V1)
c.Settings = map[string]interface{}{}
c.SecureSettings = map[string]string{}
if integrationType == "webex" {
if integrationType == webex.Type {
// Webex passes validation without any settings but should fail with an unparsable URL.
c.Settings["api_url"] = "(*^$*^%!@#$*()"
}

View File

@ -1,290 +0,0 @@
package channels_config
import (
"encoding/json"
"fmt"
"maps"
"reflect"
"slices"
"strings"
"testing"
"github.com/grafana/alerting/notify/notifytest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetSecretKeysForContactPointType(t *testing.T) {
httpConfigSecrets := []string{"http_config.authorization.credentials", "http_config.basic_auth.password", "http_config.oauth2.client_secret"}
testCases := []struct {
receiverType string
version NotifierVersion
expectedSecretFields []string
}{
{receiverType: "dingding", version: V1, expectedSecretFields: []string{"url"}},
{receiverType: "kafka", version: V1, expectedSecretFields: []string{"password"}},
{receiverType: "email", version: V1, expectedSecretFields: []string{}},
{receiverType: "pagerduty", version: V1, expectedSecretFields: []string{"integrationKey"}},
{receiverType: "victorops", version: V1, expectedSecretFields: []string{"url"}},
{receiverType: "oncall", version: V1, expectedSecretFields: []string{"password", "authorization_credentials"}},
{receiverType: "pushover", version: V1, expectedSecretFields: []string{"apiToken", "userKey"}},
{receiverType: "slack", version: V1, expectedSecretFields: []string{"token", "url"}},
{receiverType: "sensugo", version: V1, expectedSecretFields: []string{"apikey"}},
{receiverType: "teams", version: V1, expectedSecretFields: []string{}},
{receiverType: "telegram", version: V1, expectedSecretFields: []string{"bottoken"}},
{receiverType: "webhook", version: V1, expectedSecretFields: []string{
"password",
"authorization_credentials",
"tlsConfig.caCertificate",
"tlsConfig.clientCertificate",
"tlsConfig.clientKey",
"hmacConfig.secret",
"http_config.oauth2.client_secret",
"http_config.oauth2.tls_config.caCertificate",
"http_config.oauth2.tls_config.clientCertificate",
"http_config.oauth2.tls_config.clientKey",
}},
{receiverType: "wecom", version: V1, expectedSecretFields: []string{"url", "secret"}},
{receiverType: "prometheus-alertmanager", version: V1, expectedSecretFields: []string{"basicAuthPassword"}},
{receiverType: "discord", version: V1, expectedSecretFields: []string{"url"}},
{receiverType: "googlechat", version: V1, expectedSecretFields: []string{"url"}},
{receiverType: "LINE", version: V1, expectedSecretFields: []string{"token"}},
{receiverType: "threema", version: V1, expectedSecretFields: []string{"api_secret"}},
{receiverType: "opsgenie", version: V1, expectedSecretFields: []string{"apiKey"}},
{receiverType: "webex", version: V1, expectedSecretFields: []string{"bot_token"}},
{receiverType: "sns", version: V1, expectedSecretFields: []string{"sigv4.access_key", "sigv4.secret_key"}},
{receiverType: "mqtt", version: V1, expectedSecretFields: []string{"password", "tlsConfig.caCertificate", "tlsConfig.clientCertificate", "tlsConfig.clientKey"}},
{receiverType: "jira", version: V1, expectedSecretFields: []string{"user", "password", "api_token"}},
{receiverType: "victorops", version: V0mimir1, expectedSecretFields: append([]string{"api_key"}, httpConfigSecrets...)},
{receiverType: "sns", version: V0mimir1, expectedSecretFields: append([]string{"sigv4.SecretKey"}, httpConfigSecrets...)},
{receiverType: "telegram", version: V0mimir1, expectedSecretFields: append([]string{"token"}, httpConfigSecrets...)},
{receiverType: "discord", version: V0mimir1, expectedSecretFields: append([]string{"webhook_url"}, httpConfigSecrets...)},
{receiverType: "pagerduty", version: V0mimir1, expectedSecretFields: append([]string{"routing_key", "service_key"}, httpConfigSecrets...)},
{receiverType: "pushover", version: V0mimir1, expectedSecretFields: append([]string{"user_key", "token"}, httpConfigSecrets...)},
{receiverType: "jira", version: V0mimir1, expectedSecretFields: httpConfigSecrets},
{receiverType: "opsgenie", version: V0mimir1, expectedSecretFields: append([]string{"api_key"}, httpConfigSecrets...)},
{receiverType: "teams", version: V0mimir1, expectedSecretFields: append([]string{"webhook_url"}, httpConfigSecrets...)},
{receiverType: "teams", version: V0mimir2, expectedSecretFields: append([]string{"webhook_url"}, httpConfigSecrets...)},
{receiverType: "email", version: V0mimir1, expectedSecretFields: []string{"auth_password", "auth_secret"}},
{receiverType: "slack", version: V0mimir1, expectedSecretFields: append([]string{"api_url"}, httpConfigSecrets...)},
{receiverType: "webex", version: V0mimir1, expectedSecretFields: httpConfigSecrets},
{receiverType: "wechat", version: V0mimir1, expectedSecretFields: append([]string{"api_secret"}, httpConfigSecrets...)},
{receiverType: "webhook", version: V0mimir1, expectedSecretFields: append([]string{"url"}, httpConfigSecrets...)},
}
n := slices.Collect(GetAvailableNotifiersV2())
type typeWithVersion struct {
Type string
Version NotifierVersion
}
allTypes := make(map[typeWithVersion]struct{}, len(n))
getKey := func(pluginType string, version NotifierVersion) typeWithVersion {
return typeWithVersion{pluginType, version}
}
for _, p := range n {
for _, v := range p.Versions {
allTypes[getKey(p.Type, v.Version)] = struct{}{}
}
}
for _, testCase := range testCases {
delete(allTypes, getKey(testCase.receiverType, testCase.version))
t.Run(fmt.Sprintf("%s-%s", testCase.receiverType, testCase.version), func(t *testing.T) {
got, err := GetSecretKeysForContactPointType(testCase.receiverType, testCase.version)
require.NoError(t, err)
require.ElementsMatch(t, testCase.expectedSecretFields, got)
})
}
for it := range allTypes {
t.Run(fmt.Sprintf("%s-%s", it.Type, it.Version), func(t *testing.T) {
got, err := GetSecretKeysForContactPointType(it.Type, it.Version)
require.NoError(t, err)
require.Emptyf(t, got, "secret keys for version %s of %s should be empty", it.Version, it.Type)
})
}
require.Emptyf(t, allTypes, "not all types are covered: %s", allTypes)
}
func TestGetAvailableNotifiersV2(t *testing.T) {
n := slices.Collect(GetAvailableNotifiersV2())
require.NotEmpty(t, n)
for _, notifier := range n {
t.Run(fmt.Sprintf("integration %s [%s]", notifier.Type, notifier.Name), func(t *testing.T) {
currentVersion := V1
if notifier.Type == "wechat" {
currentVersion = V0mimir1
}
t.Run(fmt.Sprintf("current version is %s", currentVersion), func(t *testing.T) {
require.Equal(t, currentVersion, notifier.GetCurrentVersion().Version)
})
t.Run("should be able to create only v1", func(t *testing.T) {
for _, version := range notifier.Versions {
if version.Version == V1 {
require.True(t, version.CanCreate, "v1 should be able to create")
continue
}
require.False(t, version.CanCreate, "v0 should not be able to create")
}
})
})
}
}
func TestConfigForIntegrationType(t *testing.T) {
t.Run("should return current version for all common types", func(t *testing.T) {
for plugin := range GetAvailableNotifiersV2() {
t.Run(plugin.Type, func(t *testing.T) {
version, err := ConfigForIntegrationType(plugin.Type)
require.NoErrorf(t, err, "expected config but got error for plugin type %s", plugin.Type)
assert.Equal(t, version.Plugin, plugin)
assert.Equal(t, version, plugin.GetCurrentVersion())
})
}
})
t.Run("should return specific version if matched by alias", func(t *testing.T) {
for plugin := range GetAvailableNotifiersV2() {
for _, version := range plugin.Versions {
if version.TypeAlias == "" {
continue
}
t.Run(version.TypeAlias, func(t *testing.T) {
actualVersion, err := ConfigForIntegrationType(version.TypeAlias)
require.NoErrorf(t, err, "expected config but got error for plugin type %s", plugin.Type)
assert.Equal(t, version, actualVersion)
})
}
}
})
t.Run("should return error if not known type", func(t *testing.T) {
_, err := ConfigForIntegrationType("unknown")
require.Error(t, err)
})
}
func TestTypeUniqueness(t *testing.T) {
knownTypes := make(map[string]struct{})
for plugin := range GetAvailableNotifiersV2() {
iType := strings.ToLower(plugin.Type)
if _, ok := knownTypes[iType]; ok {
assert.Failf(t, "duplicate plugin type", "plugin type %s", plugin.Type)
}
knownTypes[iType] = struct{}{}
for _, version := range plugin.Versions {
if version.TypeAlias == "" {
continue
}
iType = strings.ToLower(version.TypeAlias)
if _, ok := knownTypes[iType]; ok {
assert.Failf(t, "mimir type duplicates Grafana plugin type", "plugin type %s", iType)
}
knownTypes[iType] = struct{}{}
}
}
}
func Test_getSecretFields(t *testing.T) {
testCases := []struct {
name string
parentPath string
options []NotifierOption
expectedFields []string
}{
{
name: "No secure fields",
parentPath: "",
options: []NotifierOption{
{PropertyName: "field1", Secure: false, SubformOptions: nil},
{PropertyName: "field2", Secure: false, SubformOptions: nil},
},
expectedFields: []string{},
},
{
name: "Single secure field",
parentPath: "",
options: []NotifierOption{
{PropertyName: "field1", Secure: true, SubformOptions: nil},
{PropertyName: "field2", Secure: false, SubformOptions: nil},
},
expectedFields: []string{"field1"},
},
{
name: "Secure field in subform",
parentPath: "parent",
options: []NotifierOption{
{PropertyName: "field1", Secure: true, SubformOptions: nil},
{PropertyName: "field2", Secure: false, SubformOptions: []NotifierOption{
{PropertyName: "subfield1", Secure: true, SubformOptions: nil},
}},
},
expectedFields: []string{"parent.field1", "parent.field2.subfield1"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := getSecretFields(tc.parentPath, tc.options)
require.ElementsMatch(t, got, tc.expectedFields)
})
}
}
func TestV0IntegrationsSecrets(t *testing.T) {
// This test ensures that all known integrations' secrets are listed in the schema definition.
notifytest.ForEachIntegrationType(t, func(configType reflect.Type) {
t.Run(configType.Name(), func(t *testing.T) {
integrationType := strings.ToLower(strings.TrimSuffix(configType.Name(), "Config"))
pluginVersion, err := ConfigForIntegrationType(integrationType)
require.NoError(t, err)
if pluginVersion.Version == V1 {
var ok bool
pluginVersion, ok = pluginVersion.Plugin.GetVersion(V0mimir1)
require.True(t, ok)
}
expectedSecrets := pluginVersion.GetSecretFieldsPaths()
var secrets []string
for option := range maps.Keys(notifytest.ValidMimirHTTPConfigs) {
cfg, err := notifytest.GetMimirIntegrationForType(configType, option)
require.NoError(t, err)
data, err := json.Marshal(cfg)
require.NoError(t, err)
m := map[string]any{}
err = json.Unmarshal(data, &m)
require.NoError(t, err)
secrets = append(secrets, getSecrets(m, "")...)
}
secrets = unique(secrets)
t.Log(secrets)
require.ElementsMatch(t, expectedSecrets, secrets)
})
})
}
func unique(slice []string) []string {
keys := make(map[string]struct{}, len(slice))
list := make([]string, 0, len(slice))
for _, entry := range slice {
if _, value := keys[entry]; !value {
keys[entry] = struct{}{}
list = append(list, entry)
}
}
return list
}
func getSecrets(m map[string]any, parent string) []string {
var result []string
for key, val := range m {
str, ok := val.(string)
if ok && str == "<secret>" {
result = append(result, parent+key)
}
m, ok := val.(map[string]any)
if ok {
subSecrets := getSecrets(m, parent+key+".")
result = append(result, subSecrets...)
}
}
return result
}

View File

@ -1,122 +0,0 @@
package channels_config
// NotifierPlugin holds meta information about a notifier.
type NotifierPlugin struct {
Type string `json:"type"`
TypeAlias string `json:"typeAlias,omitempty"`
Name string `json:"name"`
Heading string `json:"heading"`
Description string `json:"description"`
Info string `json:"info"`
Options []NotifierOption `json:"options"`
}
// VersionedNotifierPlugin represents a notifier plugin with multiple versions and detailed configuration options.
// It includes metadata such as type, name, description, and version-specific details.
type VersionedNotifierPlugin struct {
Type string `json:"type"`
CurrentVersion NotifierVersion `json:"currentVersion"`
Name string `json:"name"`
Heading string `json:"heading,omitempty"`
Description string `json:"description,omitempty"`
Info string `json:"info,omitempty"`
Versions []NotifierPluginVersion `json:"versions"`
}
// GetVersion retrieves a specific version of the notifier plugin by its version string. Returns the version and a boolean indicating success.
func (p VersionedNotifierPlugin) GetVersion(v NotifierVersion) (NotifierPluginVersion, bool) {
for _, version := range p.Versions {
if version.Version == v {
return version, true
}
}
return NotifierPluginVersion{}, false
}
// GetCurrentVersion retrieves the current version of the notifier plugin based on the CurrentVersion property.
// Panics if the version specified in CurrentVersion is not found in the configured versions.
func (p VersionedNotifierPlugin) GetCurrentVersion() NotifierPluginVersion {
v, ok := p.GetVersion(p.CurrentVersion)
if !ok {
panic("version not found for current version: " + p.CurrentVersion)
}
return v
}
// NotifierPluginVersion represents a version of a notifier plugin, including configuration options and metadata.
type NotifierPluginVersion struct {
TypeAlias string `json:"typeAlias,omitempty"`
Version NotifierVersion `json:"version"`
CanCreate bool `json:"canCreate"`
Options []NotifierOption `json:"options"`
Info string `json:"info,omitempty"`
Plugin *VersionedNotifierPlugin `json:"-"`
}
// GetSecretFieldsPaths returns a list of paths for fields marked as secure within the NotifierPluginVersion's options.
func (v NotifierPluginVersion) GetSecretFieldsPaths() []string {
return getSecretFields("", v.Options)
}
// NotifierOption holds information about options specific for the NotifierPlugin.
type NotifierOption struct {
Element ElementType `json:"element"`
InputType InputType `json:"inputType"`
Label string `json:"label"`
Description string `json:"description"`
Placeholder string `json:"placeholder"`
PropertyName string `json:"propertyName"`
SelectOptions []SelectOption `json:"selectOptions"`
ShowWhen ShowWhen `json:"showWhen"`
Required bool `json:"required"`
ValidationRule string `json:"validationRule"`
Secure bool `json:"secure"`
DependsOn string `json:"dependsOn"`
SubformOptions []NotifierOption `json:"subformOptions"`
}
// ElementType is the type of element that can be rendered in the frontend.
type ElementType string
const (
// ElementTypeInput will render an input
ElementTypeInput = "input"
// ElementTypeSelect will render a select
ElementTypeSelect = "select"
// ElementTypeCheckbox will render a checkbox
ElementTypeCheckbox = "checkbox"
// ElementTypeTextArea will render a textarea
ElementTypeTextArea = "textarea"
// ElementTypeKeyValueMap will render inputs to add arbitrary key-value pairs
ElementTypeKeyValueMap = "key_value_map"
// ElementSubformArray will render a sub-form with schema defined in SubformOptions
ElementTypeSubform = "subform"
// ElementSubformArray will render a multiple sub-forms with schema defined in SubformOptions
ElementSubformArray = "subform_array"
// ElementStringArray will render a set of fields to manage an array of strings.
ElementStringArray = "string_array"
)
// InputType is the type of input that can be rendered in the frontend.
type InputType string
const (
// InputTypeText will render a text field in the frontend
InputTypeText = "text"
// InputTypePassword will render a password field in the frontend
InputTypePassword = "password"
)
// SelectOption is a simple type for Options that have dropdown options. Should be used when Element is ElementTypeSelect.
type SelectOption struct {
Value string `json:"value"`
Label string `json:"label"`
}
// ShowWhen holds information about when options are dependant on other options.
// Should be used when Element is ElementTypeSelect.
// Does not work for ElementTypeCheckbox.
type ShowWhen struct {
Field string `json:"field"`
Is string `json:"is"`
}

View File

@ -8,10 +8,12 @@ import (
"fmt"
"strings"
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/alerting/receivers/schema"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/secrets"
@ -107,11 +109,11 @@ func encryptReceiverConfigs(c []*definitions.PostableApiReceiver, encrypt defini
return fmt.Errorf("integration '%s' of receiver '%s' has settings that cannot be parsed as JSON: %w", gr.Type, gr.Name, err)
}
secretKeys, err := channels_config.GetSecretKeysForContactPointType(gr.Type, channels_config.V1)
if err != nil {
return fmt.Errorf("failed to get secret keys for contact point type %s: %w", gr.Type, err)
typeSchema, ok := alertingNotify.GetSchemaVersionForIntegration(schema.IntegrationType(gr.Type), schema.V1)
if !ok {
return fmt.Errorf("failed to get secret keys for contact point type %s", gr.Type)
}
secretKeys := typeSchema.GetSecretFieldsPaths()
secureSettings := gr.SecureSettings
if secureSettings == nil {
secureSettings = make(map[string]string)

View File

@ -7,7 +7,7 @@ import (
alertingImages "github.com/grafana/alerting/images"
"github.com/grafana/alerting/receivers"
alertingEmail "github.com/grafana/alerting/receivers/email"
alertingEmail "github.com/grafana/alerting/receivers/email/v1"
alertingTemplates "github.com/grafana/alerting/templates"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"

View File

@ -8,13 +8,14 @@ import (
"github.com/grafana/alerting/definition"
"github.com/grafana/alerting/notify"
"github.com/grafana/alerting/receivers/schema"
"github.com/grafana/alerting/receivers/webhook"
"github.com/prometheus/alertmanager/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/util"
)
func TestReceiverInUse(t *testing.T) {
@ -91,8 +92,9 @@ func TestDeleteReceiver(t *testing.T) {
}
func TestCreateReceiver(t *testing.T) {
rawCfg := notify.AllKnownConfigsForTesting["webhook"]
cfgSchema, err := models.IntegrationConfigFromType(rawCfg.NotifierType, util.Pointer("v1"))
rawCfg := notify.AllKnownConfigsForTesting[string(webhook.Type)]
typeSchema, _ := notify.GetSchemaForIntegration(webhook.Type)
cfgSchema, err := models.IntegrationConfigFromSchema(typeSchema, schema.V1)
require.NoError(t, err)
settings := map[string]any{}
require.NoError(t, json.Unmarshal([]byte(rawCfg.Config), &settings))
@ -197,8 +199,9 @@ func TestCreateReceiver(t *testing.T) {
}
func TestUpdateReceiver(t *testing.T) {
rawCfg := notify.AllKnownConfigsForTesting["webhook"]
cfgSchema, err := models.IntegrationConfigFromType(rawCfg.NotifierType, util.Pointer("v1"))
rawCfg := notify.AllKnownConfigsForTesting[string(webhook.Type)]
typeSchema, _ := notify.GetSchemaForIntegration(webhook.Type)
cfgSchema, err := models.IntegrationConfigFromSchema(typeSchema, schema.V1)
require.NoError(t, err)
settings := map[string]any{}
require.NoError(t, json.Unmarshal([]byte(rawCfg.Config), &settings))
@ -297,8 +300,9 @@ func TestUpdateReceiver(t *testing.T) {
}
func TestGetReceiver(t *testing.T) {
rawCfg := notify.AllKnownConfigsForTesting["webhook"]
cfgSchema, err := models.IntegrationConfigFromType(rawCfg.NotifierType, util.Pointer("v1"))
rawCfg := notify.AllKnownConfigsForTesting[string(webhook.Type)]
typeSchema, _ := notify.GetSchemaForIntegration(webhook.Type)
cfgSchema, err := models.IntegrationConfigFromSchema(typeSchema, schema.V1)
require.NoError(t, err)
settings := map[string]any{}
require.NoError(t, json.Unmarshal([]byte(rawCfg.Config), &settings))

View File

@ -9,6 +9,7 @@ import (
"strings"
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/alerting/receivers/schema"
"github.com/prometheus/alertmanager/config"
"github.com/grafana/grafana/pkg/apimachinery/identity"
@ -17,7 +18,6 @@ import (
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/secrets"
@ -247,11 +247,11 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in
if err != nil {
return err
}
secretKeys, err := channels_config.GetSecretKeysForContactPointType(contactPoint.Type, channels_config.V1)
if err != nil {
return fmt.Errorf("%w: %s", ErrValidation, err.Error())
typeSchema, ok := alertingNotify.GetSchemaVersionForIntegration(schema.IntegrationType(contactPoint.Type), schema.V1)
if !ok {
return fmt.Errorf("%w: failed to get secret keys for contact point type %s", ErrValidation, contactPoint.Type)
}
for _, secretKey := range secretKeys {
for _, secretKey := range typeSchema.GetSecretFieldsPaths() {
secretValue := contactPoint.Settings.Get(secretKey).MustString()
if secretValue == apimodels.RedactedValue {
contactPoint.Settings.Set(secretKey, rawContactPoint.Settings.Get(secretKey).MustString())
@ -522,11 +522,11 @@ func ValidateContactPoint(ctx context.Context, e apimodels.EmbeddedContactPoint,
// 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{}
secretKeys, err := channels_config.GetSecretKeysForContactPointType(e.Type, channels_config.V1)
if err != nil {
return nil, err
typeSchema, ok := alertingNotify.GetSchemaVersionForIntegration(schema.IntegrationType(e.Type), schema.V1)
if !ok {
return nil, fmt.Errorf("failed to get secret keys for contact point type %s", e.Type)
}
for _, secretKey := range secretKeys {
for _, secretKey := range typeSchema.GetSecretFieldsPaths() {
secretValue, err := extractCaseInsensitive(e.Settings, secretKey)
if err != nil {
return nil, err

View File

@ -9,6 +9,7 @@ import (
"testing"
"github.com/grafana/alerting/notify"
"github.com/grafana/alerting/receivers/schema"
"github.com/prometheus/alertmanager/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -25,7 +26,6 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
"github.com/grafana/grafana/pkg/services/secrets"
@ -441,15 +441,14 @@ func TestRemoveSecretsForContactPoint(t *testing.T) {
keys := maps.Keys(configs)
slices.Sort(keys)
for _, integrationType := range keys {
integration := models.IntegrationGen(models.IntegrationMuts.WithValidConfig(integrationType))()
integration := models.IntegrationGen(models.IntegrationMuts.WithValidConfig(schema.IntegrationType(integrationType)))()
if f, ok := overrides[integrationType]; ok {
f(integration.Settings)
}
settingsRaw, err := json.Marshal(integration.Settings)
require.NoError(t, err)
expectedFields, err := channels_config.GetSecretKeysForContactPointType(integrationType, channels_config.V1)
require.NoError(t, err)
typeSchema, _ := notify.GetSchemaVersionForIntegration(schema.IntegrationType(integrationType), schema.V1)
expectedFields := typeSchema.GetSecretFieldsPaths()
t.Run(integrationType, func(t *testing.T) {
cp := definitions.EmbeddedContactPoint{

View File

@ -13,7 +13,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/tests/testinfra"
@ -54,10 +53,17 @@ func TestIntegrationAvailableChannels(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)
expNotifiers := channels_config.GetAvailableNotifiers()
expJson, err := json.Marshal(expNotifiers)
expectedBytes, err := os.ReadFile(path.Join("test-data", "alert-notifiers-v1-snapshot.json"))
require.NoError(t, err)
require.Equal(t, string(expJson), string(b))
require.NoError(t, err)
if !assert.JSONEq(t, string(expectedBytes), string(b)) {
var prettyJSON bytes.Buffer
err := json.Indent(&prettyJSON, b, "", " ")
require.NoError(t, err)
err = os.WriteFile(path.Join("test-data", "alert-notifiers-v1-snapshot.json"), prettyJSON.Bytes(), 0o644)
require.NoError(t, err)
}
})
t.Run("should return versioned notifiers", func(t *testing.T) {

View File

@ -17,11 +17,11 @@ import (
"time"
"github.com/grafana/alerting/receivers"
alertingLine "github.com/grafana/alerting/receivers/line"
alertingPushover "github.com/grafana/alerting/receivers/pushover"
alertingSlack "github.com/grafana/alerting/receivers/slack"
alertingTelegram "github.com/grafana/alerting/receivers/telegram"
alertingThreema "github.com/grafana/alerting/receivers/threema"
alertingLine "github.com/grafana/alerting/receivers/line/v1"
alertingPushover "github.com/grafana/alerting/receivers/pushover/v1"
alertingSlack "github.com/grafana/alerting/receivers/slack/v1"
alertingTelegram "github.com/grafana/alerting/receivers/telegram/v1"
alertingThreema "github.com/grafana/alerting/receivers/threema/v1"
alertingTemplates "github.com/grafana/alerting/templates"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/common/model"

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@
"description": "Send notifications to LINE notify",
"versions": [
{
"typeAlias": "line",
"version": "v1",
"canCreate": true,
"options": [
@ -8143,7 +8144,7 @@
"type": "sns",
"currentVersion": "v1",
"name": "AWS SNS",
"heading": "Webex settings",
"heading": "AWS SNS settings",
"description": "Sends notifications to AWS Simple Notification Service",
"versions": [
{

View File

@ -13,6 +13,7 @@ import (
"strings"
"testing"
"github.com/grafana/alerting/receivers/schema"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/errors"
@ -37,7 +38,6 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/api"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/tests/api/alerting"
@ -1311,12 +1311,14 @@ func TestIntegrationCRUD(t *testing.T) {
require.Equal(t, receiver, get)
t.Run("should return secrets in secureFields but not settings", func(t *testing.T) {
for _, integration := range get.Spec.Integrations {
integrationType := schema.IntegrationType(integration.Type)
t.Run(integration.Type, func(t *testing.T) {
expected := notify.AllKnownConfigsForTesting[strings.ToLower(integration.Type)]
var fields map[string]any
require.NoError(t, json.Unmarshal([]byte(expected.Config), &fields))
secretFields, err := channels_config.GetSecretKeysForContactPointType(integration.Type, channels_config.V1)
require.NoError(t, err)
typeSchema, ok := notify.GetSchemaVersionForIntegration(integrationType, schema.V1)
require.True(t, ok)
secretFields := typeSchema.GetSecretFieldsPaths()
for _, field := range secretFields {
if _, ok := fields[field]; !ok { // skip field that is not in the original setting
continue