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
23 changed files with 4475 additions and 4255 deletions
+21 -17
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,
+35 -38
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))
+13 -10
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"] = "(*^$*^%!@#$*()"
}