The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go

405 lines
9.5 KiB

package definitions
import (
"encoding/json"
"os"
"strings"
"testing"
"github.com/grafana/alerting/definition"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func Test_GettableUserConfigUnmarshaling(t *testing.T) {
for _, tc := range []struct {
desc, input string
output GettableUserConfig
err bool
}{
{
desc: "empty",
input: ``,
output: GettableUserConfig{},
},
{
desc: "empty-ish",
input: `
template_files: {}
alertmanager_config: ""
`,
output: GettableUserConfig{
TemplateFiles: map[string]string{},
},
},
{
desc: "bad type for template",
input: `
template_files: abc
alertmanager_config: ""
`,
err: true,
},
{
desc: "existing templates",
input: `
template_files:
foo: bar
alertmanager_config: ""
`,
output: GettableUserConfig{
TemplateFiles: map[string]string{"foo": "bar"},
},
},
{
desc: "existing templates inline",
input: `
template_files: {foo: bar}
alertmanager_config: ""
`,
output: GettableUserConfig{
TemplateFiles: map[string]string{"foo": "bar"},
},
},
{
desc: "existing am config",
input: `
template_files: {foo: bar}
alertmanager_config: |
route:
receiver: am
continue: false
routes:
- receiver: am
continue: false
templates: []
receivers:
- name: am
email_configs:
- to: foo
from: bar
headers:
Bazz: buzz
text: hi
html: there
`,
output: GettableUserConfig{
TemplateFiles: map[string]string{"foo": "bar"},
AlertmanagerConfig: GettableApiAlertingConfig{
Config: Config{
Templates: []string{},
Route: &Route{
Receiver: "am",
Routes: []*Route{
{
Receiver: "am",
},
},
},
},
Receivers: []*GettableApiReceiver{
{
Receiver: config.Receiver{
Name: "am",
EmailConfigs: []*config.EmailConfig{{
To: "foo",
From: "bar",
Headers: map[string]string{
"Bazz": "buzz",
},
Text: "hi",
HTML: "there",
}},
},
},
},
},
},
},
} {
t.Run(tc.desc, func(t *testing.T) {
var out GettableUserConfig
err := yaml.Unmarshal([]byte(tc.input), &out)
if tc.err {
require.Error(t, err)
return
}
require.Nil(t, err)
// Override the map[string]any field for test simplicity.
// It's tested in Test_GettableUserConfigRoundtrip.
out.amSimple = nil
require.Equal(t, tc.output, out)
})
}
}
func Test_GettableUserConfigRoundtrip(t *testing.T) {
// raw contains secret fields. We'll unmarshal, re-marshal, and ensure
// the fields are not redacted.
yamlEncoded, err := os.ReadFile("alertmanager_test_artifact.yaml")
require.Nil(t, err)
jsonEncoded, err := os.ReadFile("alertmanager_test_artifact.json")
require.Nil(t, err)
// test GettableUserConfig (yamlDecode -> jsonEncode)
var tmp GettableUserConfig
require.Nil(t, yaml.Unmarshal(yamlEncoded, &tmp))
out, err := json.MarshalIndent(&tmp, "", " ")
require.Nil(t, err)
require.Equal(t, strings.TrimSpace(string(jsonEncoded)), string(out))
// test PostableUserConfig (jsonDecode -> yamlEncode)
var tmp2 PostableUserConfig
require.Nil(t, json.Unmarshal(jsonEncoded, &tmp2))
out, err = yaml.Marshal(&tmp2)
require.Nil(t, err)
require.Equal(t, string(yamlEncoded), string(out))
}
func Test_Marshaling_Validation(t *testing.T) {
jsonEncoded, err := os.ReadFile("alertmanager_test_artifact.json")
require.Nil(t, err)
var tmp GettableUserConfig
require.Nil(t, json.Unmarshal(jsonEncoded, &tmp))
expected := []model.LabelName{"alertname"}
require.Equal(t, expected, tmp.AlertmanagerConfig.Route.GroupBy)
}
func Test_RawMessageMarshaling(t *testing.T) {
type Data struct {
Field RawMessage `json:"field" yaml:"field"`
}
t.Run("should unmarshal nil", func(t *testing.T) {
v := Data{
Field: nil,
}
data, err := json.Marshal(v)
require.NoError(t, err)
assert.JSONEq(t, `{ "field": null }`, string(data))
var n Data
require.NoError(t, json.Unmarshal(data, &n))
assert.Equal(t, RawMessage("null"), n.Field)
data, err = yaml.Marshal(&v)
require.NoError(t, err)
assert.Equal(t, "field: null\n", string(data))
require.NoError(t, yaml.Unmarshal(data, &n))
assert.Nil(t, n.Field)
})
t.Run("should unmarshal value", func(t *testing.T) {
v := Data{
Field: RawMessage(`{ "data": "test"}`),
}
data, err := json.Marshal(v)
require.NoError(t, err)
assert.JSONEq(t, `{"field":{"data":"test"}}`, string(data))
var n Data
require.NoError(t, json.Unmarshal(data, &n))
assert.Equal(t, RawMessage(`{"data":"test"}`), n.Field)
data, err = yaml.Marshal(&v)
require.NoError(t, err)
assert.Equal(t, "field:\n data: test\n", string(data))
require.NoError(t, yaml.Unmarshal(data, &n))
assert.Equal(t, RawMessage(`{"data":"test"}`), n.Field)
})
}
func TestPostableUserConfig_GetMergedAlertmanagerConfig(t *testing.T) {
alertmanagerCfg := PostableApiAlertingConfig{
Config: Config{
Route: &Route{
Receiver: "default",
},
},
Receivers: []*PostableApiReceiver{
{
Receiver: config.Receiver{
Name: "default",
},
},
},
}
testCases := []struct {
name string
config PostableUserConfig
expectedError string
}{
{
name: "no extra configs",
config: PostableUserConfig{
AlertmanagerConfig: alertmanagerCfg,
},
},
{
name: "valid mimir config",
config: PostableUserConfig{
AlertmanagerConfig: alertmanagerCfg,
ExtraConfigs: []ExtraConfiguration{
{
Identifier: "mimir-1",
MergeMatchers: config.Matchers{
{
Type: labels.MatchEqual,
Name: "cluster",
Value: "prod",
},
},
AlertmanagerConfig: `route:
receiver: mimir-receiver
receivers:
- name: mimir-receiver`,
},
},
},
},
{
name: "empty identifier",
config: PostableUserConfig{
AlertmanagerConfig: alertmanagerCfg,
ExtraConfigs: []ExtraConfiguration{
{
Identifier: "",
MergeMatchers: config.Matchers{},
AlertmanagerConfig: `{
"route": {
"receiver": "test"
}
}`,
},
},
},
expectedError: "invalid merge options",
},
{
name: "bad matcher type",
config: PostableUserConfig{
AlertmanagerConfig: alertmanagerCfg,
ExtraConfigs: []ExtraConfiguration{
{
Identifier: "test",
MergeMatchers: config.Matchers{
{
Type: labels.MatchNotEqual,
Name: "cluster",
Value: "prod",
},
},
},
},
},
expectedError: "only equality matchers are allowed",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := tc.config.GetMergedAlertmanagerConfig()
if tc.expectedError != "" {
require.Error(t, err)
require.ErrorContains(t, err, tc.expectedError)
} else {
require.NoError(t, err)
require.NotNil(t, result.Config)
}
})
}
}
func TestPostableUserConfig_GetMergedTemplateDefinitions(t *testing.T) {
testCases := []struct {
name string
config PostableUserConfig
expectedTemplates int
}{
{
name: "no templates",
config: PostableUserConfig{
TemplateFiles: map[string]string{},
ExtraConfigs: []ExtraConfiguration{},
},
expectedTemplates: 0,
},
{
name: "grafana templates only",
config: PostableUserConfig{
TemplateFiles: map[string]string{
"grafana-template1": "{{ define \"test\" }}Hello{{ end }}",
"grafana-template2": "{{ define \"test2\" }}World{{ end }}",
},
ExtraConfigs: []ExtraConfiguration{},
},
expectedTemplates: 2,
},
{
name: "mimir templates only",
config: PostableUserConfig{
TemplateFiles: map[string]string{},
ExtraConfigs: []ExtraConfiguration{
{
TemplateFiles: map[string]string{
"mimir-template": "{{ define \"mimir\" }}Mimir{{ end }}",
},
},
},
},
expectedTemplates: 1,
},
{
name: "mixed templates",
config: PostableUserConfig{
TemplateFiles: map[string]string{
"grafana-template": "{{ define \"grafana\" }}Grafana{{ end }}",
},
ExtraConfigs: []ExtraConfiguration{
{
TemplateFiles: map[string]string{
"mimir-template": "{{ define \"mimir\" }}Mimir{{ end }}",
},
},
},
},
expectedTemplates: 2,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := tc.config.GetMergedTemplateDefinitions()
require.Len(t, result, tc.expectedTemplates)
templateMap := make(map[string]string)
kindMap := make(map[string]definition.TemplateKind)
for _, tmpl := range result {
templateMap[tmpl.Name] = tmpl.Content
kindMap[tmpl.Name] = tmpl.Kind
}
for name, content := range tc.config.TemplateFiles {
require.Equal(t, content, templateMap[name])
require.Equal(t, definition.GrafanaTemplateKind, kindMap[name])
}
if len(tc.config.ExtraConfigs) > 0 {
for name, content := range tc.config.ExtraConfigs[0].TemplateFiles {
require.Equal(t, content, templateMap[name])
require.Equal(t, definition.MimirTemplateKind, kindMap[name])
}
}
})
}
}