mirror of https://github.com/grafana/grafana.git
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:
parent
a8bff45256
commit
b8f23eacd4
|
@ -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
|
||||
|
|
|
@ -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
2
go.mod
|
@ -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
4
go.sum
|
@ -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=
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"] = "(*^$*^%!@#$*()"
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
@ -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": [
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue