Alerting: Ensure k8s receiver API create/update will never store nil settings (#92701)

Ensure Create/Update will never store nil Settings
This commit is contained in:
Matthew Jacobson 2024-08-29 13:00:55 -04:00 committed by GitHub
parent 8774642788
commit e43ddd516d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 69 additions and 9 deletions

View File

@ -32,13 +32,17 @@ func IntegrationToPostableGrafanaReceiver(integration *models.Integration) (*api
SecureSettings: maps.Clone(integration.SecureSettings), SecureSettings: maps.Clone(integration.SecureSettings),
} }
if len(integration.Settings) > 0 { // Alertmanager will fail validation with nil Settings , so ensure we always have at least an empty map.
jsonBytes, err := json.Marshal(integration.Settings) settings := integration.Settings
if err != nil { if settings == nil {
return nil, err settings = make(map[string]any)
}
postable.Settings = jsonBytes
} }
jsonBytes, err := json.Marshal(settings)
if err != nil {
return nil, err
}
postable.Settings = jsonBytes
return postable, nil return postable, nil
} }

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/prometheus/alertmanager/config"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -342,6 +343,7 @@ func TestReceiverService_Create(t *testing.T) {
slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("slack"))() slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("slack"))()
emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))() emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))()
lineIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("line"))()
baseReceiver := models.ReceiverGen(models.ReceiverMuts.WithName("test receiver"), models.ReceiverMuts.WithIntegrations(slackIntegration))() baseReceiver := models.ReceiverGen(models.ReceiverMuts.WithName("test receiver"), models.ReceiverMuts.WithIntegrations(slackIntegration))()
for _, tc := range []struct { for _, tc := range []struct {
@ -349,6 +351,7 @@ func TestReceiverService_Create(t *testing.T) {
user identity.Requester user identity.Requester
receiver models.Receiver receiver models.Receiver
expectedCreate models.Receiver expectedCreate models.Receiver
expectedStored *definitions.PostableApiReceiver
expectedErr error expectedErr error
expectedProvenances map[string]models.Provenance expectedProvenances map[string]models.Provenance
}{ }{
@ -414,6 +417,49 @@ func TestReceiverService_Create(t *testing.T) {
receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithInvalidIntegration("slack")), receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithInvalidIntegration("slack")),
expectedErr: legacy_storage.ErrReceiverInvalid, expectedErr: legacy_storage.ErrReceiverInvalid,
}, },
{
name: "create integration with no normal settings should not store nil settings",
user: writer,
receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithIntegrations(
models.CopyIntegrationWith(lineIntegration,
models.IntegrationMuts.WithSettings(
map[string]any{ // Line is valid with only the single secure field "token", so Settings will be empty when saving.
"token": "secret",
},
)),
)),
expectedCreate: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithIntegrations(
models.CopyIntegrationWith(lineIntegration,
models.IntegrationMuts.WithSettings(
map[string]any{}, // Empty settings, not nil.
),
models.IntegrationMuts.WithSecureSettings(
map[string]string{
"token": "c2VjcmV0", // base64 encoded "secret".
},
),
),
)),
expectedStored: &definitions.PostableApiReceiver{
Receiver: config.Receiver{
Name: lineIntegration.Name,
},
PostableGrafanaReceivers: definitions.PostableGrafanaReceivers{
GrafanaManagedReceivers: []*definitions.PostableGrafanaReceiver{
{
UID: lineIntegration.UID,
Name: lineIntegration.Name,
Type: lineIntegration.Config.Type,
DisableResolveMessage: lineIntegration.DisableResolveMessage,
Settings: definitions.RawMessage(`{}`), // Empty settings, not nil.
SecureSettings: map[string]string{
"token": "c2VjcmV0", // base64 encoded "secret".
},
},
},
},
},
},
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
sut := createReceiverServiceSut(t, &secretsService) sut := createReceiverServiceSut(t, &secretsService)
@ -462,9 +508,19 @@ func TestReceiverService_Create(t *testing.T) {
decrypted.Version = tc.expectedCreate.Version // Version is calculated before decryption. decrypted.Version = tc.expectedCreate.Version // Version is calculated before decryption.
assert.Equal(t, decrypted, *stored) assert.Equal(t, decrypted, *stored)
provenances, err := sut.provisioningStore.GetProvenances(context.Background(), tc.user.GetOrgID(), (&definitions.EmbeddedContactPoint{}).ResourceType()) if tc.expectedProvenances != nil {
require.NoError(t, err) provenances, err := sut.provisioningStore.GetProvenances(context.Background(), tc.user.GetOrgID(), (&definitions.EmbeddedContactPoint{}).ResourceType())
assert.Equal(t, tc.expectedProvenances, provenances) require.NoError(t, err)
assert.Equal(t, tc.expectedProvenances, provenances)
}
if tc.expectedStored != nil {
revision, err := sut.cfgStore.Get(context.Background(), writer.GetOrgID())
require.NoError(t, err)
rcv, err := revision.GetReceiver(legacy_storage.NameToUid(tc.expectedStored.Name))
require.NoError(t, err)
assert.Equal(t, tc.expectedStored, rcv)
}
}) })
} }
} }