2023-10-11 14:21:21 -05:00
|
|
|
|
package ualert
|
2021-10-22 10:11:06 +01:00
|
|
|
|
|
|
|
|
|
|
import (
|
2023-10-11 14:21:21 -05:00
|
|
|
|
"encoding/base64"
|
2022-04-20 22:02:23 -04:00
|
|
|
|
"encoding/json"
|
2021-10-22 10:11:06 +01:00
|
|
|
|
"fmt"
|
2023-10-11 14:21:21 -05:00
|
|
|
|
"sort"
|
2022-10-18 00:47:39 -04:00
|
|
|
|
"strings"
|
2021-10-22 10:11:06 +01:00
|
|
|
|
"testing"
|
|
|
|
|
|
|
2023-10-11 14:21:21 -05:00
|
|
|
|
"github.com/prometheus/alertmanager/pkg/labels"
|
2022-05-30 16:47:15 +01:00
|
|
|
|
"github.com/stretchr/testify/require"
|
2022-12-29 15:15:29 -05:00
|
|
|
|
|
2023-10-11 14:21:21 -05:00
|
|
|
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
2022-12-29 15:15:29 -05:00
|
|
|
|
"github.com/grafana/grafana/pkg/util"
|
2021-10-22 10:11:06 +01:00
|
|
|
|
)
|
|
|
|
|
|
|
2023-10-11 14:21:21 -05:00
|
|
|
|
var MigTitle = migTitle
|
|
|
|
|
|
var RmMigTitle = rmMigTitle
|
|
|
|
|
|
var ClearMigrationEntryTitle = clearMigrationEntryTitle
|
|
|
|
|
|
|
|
|
|
|
|
type RmMigration = rmMigration
|
|
|
|
|
|
|
|
|
|
|
|
// UnmarshalJSON implements the json.Unmarshaler interface for Matchers. Vendored from definitions.ObjectMatchers.
|
|
|
|
|
|
func (m *ObjectMatchers) UnmarshalJSON(data []byte) error {
|
|
|
|
|
|
var rawMatchers [][3]string
|
|
|
|
|
|
if err := json.Unmarshal(data, &rawMatchers); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, rawMatcher := range rawMatchers {
|
|
|
|
|
|
var matchType labels.MatchType
|
|
|
|
|
|
switch rawMatcher[1] {
|
|
|
|
|
|
case "=":
|
|
|
|
|
|
matchType = labels.MatchEqual
|
|
|
|
|
|
case "!=":
|
|
|
|
|
|
matchType = labels.MatchNotEqual
|
|
|
|
|
|
case "=~":
|
|
|
|
|
|
matchType = labels.MatchRegexp
|
|
|
|
|
|
case "!~":
|
|
|
|
|
|
matchType = labels.MatchNotRegexp
|
|
|
|
|
|
default:
|
|
|
|
|
|
return fmt.Errorf("unsupported match type %q in matcher", rawMatcher[1])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
rawMatcher[2] = strings.TrimPrefix(rawMatcher[2], "\"")
|
|
|
|
|
|
rawMatcher[2] = strings.TrimSuffix(rawMatcher[2], "\"")
|
|
|
|
|
|
|
|
|
|
|
|
matcher, err := labels.NewMatcher(matchType, rawMatcher[0], rawMatcher[2])
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
*m = append(*m, matcher)
|
|
|
|
|
|
}
|
|
|
|
|
|
sort.Sort(labels.Matchers(*m))
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-22 10:11:06 +01:00
|
|
|
|
func Test_validateAlertmanagerConfig(t *testing.T) {
|
|
|
|
|
|
tc := []struct {
|
|
|
|
|
|
name string
|
2023-10-11 14:21:21 -05:00
|
|
|
|
receivers []*PostableGrafanaReceiver
|
2021-10-22 10:11:06 +01:00
|
|
|
|
err error
|
|
|
|
|
|
}{
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "when a slack receiver does not have a valid URL - it should error",
|
2023-10-11 14:21:21 -05:00
|
|
|
|
receivers: []*PostableGrafanaReceiver{
|
2021-10-22 10:11:06 +01:00
|
|
|
|
{
|
2023-04-13 12:25:32 -04:00
|
|
|
|
UID: "test-uid",
|
2021-10-22 10:11:06 +01:00
|
|
|
|
Name: "SlackWithBadURL",
|
|
|
|
|
|
Type: "slack",
|
2023-10-11 14:21:21 -05:00
|
|
|
|
Settings: simplejson.NewFromAny(map[string]interface{}{}),
|
2021-10-22 10:11:06 +01:00
|
|
|
|
SecureSettings: map[string]string{"url": invalidUri},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
2023-04-25 13:39:46 -04:00
|
|
|
|
err: fmt.Errorf("failed to validate integration \"SlackWithBadURL\" (UID test-uid) of type \"slack\": invalid URL %q", invalidUri),
|
2021-10-22 10:11:06 +01:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2021-10-27 13:58:37 -03:00
|
|
|
|
name: "when a slack receiver has an invalid recipient - it should not error",
|
2023-10-11 14:21:21 -05:00
|
|
|
|
receivers: []*PostableGrafanaReceiver{
|
2021-10-22 10:11:06 +01:00
|
|
|
|
{
|
|
|
|
|
|
UID: util.GenerateShortUID(),
|
|
|
|
|
|
Name: "SlackWithBadRecipient",
|
|
|
|
|
|
Type: "slack",
|
2023-10-11 14:21:21 -05:00
|
|
|
|
Settings: simplejson.NewFromAny(map[string]interface{}{"recipient": "this passes"}),
|
2021-10-22 10:11:06 +01:00
|
|
|
|
SecureSettings: map[string]string{"url": "http://webhook.slack.com/myuser"},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: "when the configuration is valid - it should not error",
|
2023-10-11 14:21:21 -05:00
|
|
|
|
receivers: []*PostableGrafanaReceiver{
|
2021-10-22 10:11:06 +01:00
|
|
|
|
{
|
|
|
|
|
|
UID: util.GenerateShortUID(),
|
|
|
|
|
|
Name: "SlackWithBadURL",
|
|
|
|
|
|
Type: "slack",
|
2023-10-11 14:21:21 -05:00
|
|
|
|
Settings: simplejson.NewFromAny(map[string]interface{}{"recipient": "#a-good-channel"}),
|
2021-10-22 10:11:06 +01:00
|
|
|
|
SecureSettings: map[string]string{"url": "http://webhook.slack.com/myuser"},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for _, tt := range tc {
|
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2023-10-11 14:21:21 -05:00
|
|
|
|
mg := newTestMigration(t)
|
2021-10-22 10:11:06 +01:00
|
|
|
|
|
|
|
|
|
|
config := configFromReceivers(t, tt.receivers)
|
2023-10-11 14:21:21 -05:00
|
|
|
|
require.NoError(t, config.EncryptSecureSettings()) // make sure we encrypt the settings
|
2023-04-13 12:25:32 -04:00
|
|
|
|
err := mg.validateAlertmanagerConfig(config)
|
2021-10-22 10:11:06 +01:00
|
|
|
|
if tt.err != nil {
|
|
|
|
|
|
require.Error(t, err)
|
|
|
|
|
|
require.EqualError(t, err, tt.err.Error())
|
|
|
|
|
|
} else {
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-11 14:21:21 -05:00
|
|
|
|
func configFromReceivers(t *testing.T, receivers []*PostableGrafanaReceiver) *PostableUserConfig {
|
2021-10-22 10:11:06 +01:00
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
2023-10-11 14:21:21 -05:00
|
|
|
|
return &PostableUserConfig{
|
|
|
|
|
|
AlertmanagerConfig: PostableApiAlertingConfig{
|
|
|
|
|
|
Receivers: []*PostableApiReceiver{
|
|
|
|
|
|
{GrafanaManagedReceivers: receivers},
|
2021-10-22 10:11:06 +01:00
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-11 14:21:21 -05:00
|
|
|
|
func (c *PostableUserConfig) EncryptSecureSettings() error {
|
2022-04-26 10:17:30 -04:00
|
|
|
|
for _, r := range c.AlertmanagerConfig.Receivers {
|
|
|
|
|
|
for _, gr := range r.GrafanaManagedReceivers {
|
2023-10-11 14:21:21 -05:00
|
|
|
|
encryptedData := GetEncryptedJsonData(gr.SecureSettings)
|
|
|
|
|
|
for k, v := range encryptedData {
|
|
|
|
|
|
gr.SecureSettings[k] = base64.StdEncoding.EncodeToString(v)
|
2022-04-26 10:17:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-22 10:11:06 +01:00
|
|
|
|
const invalidUri = "<22>6<EFBFBD>M<EFBFBD><4D>)uk譹1(<28>h`$<24>o<EFBFBD>N>mĕ<6D><C495><EFBFBD><EFBFBD>cS2<53>dh![ę<> <09><><EFBFBD>`csB<73>!<21><>OSxP<78>{<7B>"
|
2022-01-21 10:24:41 -05:00
|
|
|
|
|
|
|
|
|
|
func Test_getAlertFolderNameFromDashboard(t *testing.T) {
|
|
|
|
|
|
t.Run("should include full title", func(t *testing.T) {
|
2023-10-11 14:21:21 -05:00
|
|
|
|
dash := &dashboard{
|
|
|
|
|
|
Uid: util.GenerateShortUID(),
|
2022-01-21 10:24:41 -05:00
|
|
|
|
Title: "TEST",
|
|
|
|
|
|
}
|
|
|
|
|
|
folder := getAlertFolderNameFromDashboard(dash)
|
|
|
|
|
|
require.Contains(t, folder, dash.Title)
|
2023-10-11 14:21:21 -05:00
|
|
|
|
require.Contains(t, folder, dash.Uid)
|
2022-01-21 10:24:41 -05:00
|
|
|
|
})
|
|
|
|
|
|
t.Run("should cut title to the length", func(t *testing.T) {
|
|
|
|
|
|
title := ""
|
|
|
|
|
|
for {
|
|
|
|
|
|
title += util.GenerateShortUID()
|
|
|
|
|
|
if len(title) > MaxFolderName {
|
|
|
|
|
|
title = title[:MaxFolderName]
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-10-11 14:21:21 -05:00
|
|
|
|
dash := &dashboard{
|
|
|
|
|
|
Uid: util.GenerateShortUID(),
|
2022-01-21 10:24:41 -05:00
|
|
|
|
Title: title,
|
|
|
|
|
|
}
|
|
|
|
|
|
folder := getAlertFolderNameFromDashboard(dash)
|
|
|
|
|
|
require.Len(t, folder, MaxFolderName)
|
2023-10-11 14:21:21 -05:00
|
|
|
|
require.Contains(t, folder, dash.Uid)
|
2022-01-21 10:24:41 -05:00
|
|
|
|
})
|
|
|
|
|
|
}
|
2022-12-29 15:15:29 -05:00
|
|
|
|
|
|
|
|
|
|
func Test_shortUIDCaseInsensitiveConflicts(t *testing.T) {
|
2023-10-11 14:21:21 -05:00
|
|
|
|
s := uidSet{
|
2022-12-29 15:15:29 -05:00
|
|
|
|
set: make(map[string]struct{}),
|
|
|
|
|
|
caseInsensitive: true,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 10000 uids seems to be enough to cause a collision in almost every run if using util.GenerateShortUID directly.
|
|
|
|
|
|
for i := 0; i < 10000; i++ {
|
2023-10-11 14:21:21 -05:00
|
|
|
|
_, _ = s.generateUid()
|
2022-12-29 15:15:29 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// check if any are case-insensitive duplicates.
|
|
|
|
|
|
deduped := make(map[string]struct{})
|
|
|
|
|
|
for k := range s.set {
|
|
|
|
|
|
deduped[strings.ToLower(k)] = struct{}{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
require.Equal(t, len(s.set), len(deduped))
|
|
|
|
|
|
}
|