Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add YAML unmarshalers for Alertmanager configuration structs #186

Merged
merged 1 commit into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 28 additions & 25 deletions definition/alertmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/common/model"
"gopkg.in/yaml.v3"
)

type Provenance string
Expand All @@ -22,7 +23,7 @@ type Config struct {
// MuteTimeIntervals is deprecated and will be removed before Alertmanager 1.0.
MuteTimeIntervals []config.MuteTimeInterval `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty"`
TimeIntervals []config.TimeInterval `yaml:"time_intervals,omitempty" json:"time_intervals,omitempty"`
Templates []string `yaml:"templates" json:"templates"`
Templates []string `yaml:"templates,omitempty" json:"templates,omitempty"`
}

// A Route is a node that contains definitions of how to handle alerts. This is modified
Expand Down Expand Up @@ -125,17 +126,19 @@ func (r *Route) ResourceID() string {
// post-validation is included in the UnmarshalYAML method. Here we simply run this with
// a noop unmarshaling function in order to benefit from said validation.
func (c *Config) UnmarshalJSON(b []byte) error {
return yaml.Unmarshal(b, c)
}

func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain Config
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
if err := unmarshal((*plain)(c)); err != nil {
return err
}

noopUnmarshal := func(_ interface{}) error { return nil }

if c.Global != nil {
if err := c.Global.UnmarshalYAML(noopUnmarshal); err != nil {
return err
}
// Having a nil global config causes panics in the Alertmanager codebase.
if c.Global == nil {
c.Global = &config.GlobalConfig{}
*c.Global = config.DefaultGlobalConfig()
}

if c.Route == nil {
Expand All @@ -148,7 +151,7 @@ func (c *Config) UnmarshalJSON(b []byte) error {
}

for _, r := range c.InhibitRules {
if err := r.UnmarshalYAML(noopUnmarshal); err != nil {
if err := r.UnmarshalYAML(unmarshal); err != nil {
return err
}
}
Expand Down Expand Up @@ -215,19 +218,23 @@ func (c *PostableApiAlertingConfig) GetRoute() *Route {
}

func (c *PostableApiAlertingConfig) UnmarshalJSON(b []byte) error {
return yaml.Unmarshal(b, c)
}

func (c *PostableApiAlertingConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain PostableApiAlertingConfig
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
if err := unmarshal((*plain)(c)); err != nil {
return err
}

// Since Config implements json.Unmarshaler, we must handle _all_ other fields independently.
// Since Config implements yaml.Unmarshaler, we must handle _all_ other fields independently.
// Otherwise, the json decoder will detect this and only use the embedded type.
// Additionally, we'll use pointers to slices in order to reference the intended target.
type overrides struct {
Receivers *[]*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
Receivers *[]*PostableApiReceiver `yaml:"receivers" json:"receivers,omitempty"`
}

if err := json.Unmarshal(b, &overrides{Receivers: &c.Receivers}); err != nil {
if err := unmarshal(&overrides{Receivers: &c.Receivers}); err != nil {
return err
}

Expand Down Expand Up @@ -371,8 +378,8 @@ type PostableGrafanaReceiver struct {
Name string `json:"name"`
Type string `json:"type"`
DisableResolveMessage bool `json:"disableResolveMessage"`
Settings RawMessage `json:"settings,omitempty"`
SecureSettings map[string]string `json:"secureSettings"`
Settings RawMessage `json:"settings,omitempty" yaml:"settings,omitempty"`
SecureSettings map[string]string `json:"secureSettings,omitempty" yaml:"secureSettings,omitempty"`
}

type ReceiverType int
Expand Down Expand Up @@ -522,21 +529,17 @@ type PostableApiReceiver struct {
PostableGrafanaReceivers `yaml:",inline"`
}

func (r *PostableApiReceiver) UnmarshalJSON(b []byte) error {
return yaml.Unmarshal(b, r)
}

func (r *PostableApiReceiver) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal(&r.PostableGrafanaReceivers); err != nil {
return err
}

if err := unmarshal(&r.Receiver); err != nil {
return err
}

return nil
}

func (r *PostableApiReceiver) UnmarshalJSON(b []byte) error {
type plain PostableApiReceiver
if err := json.Unmarshal(b, (*plain)(r)); err != nil {
type plain config.Receiver
if err := unmarshal((*plain)(&r.Receiver)); err != nil {
return err
}

Expand Down
75 changes: 62 additions & 13 deletions definition/alertmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ func Test_ApiReceiver_Marshaling(t *testing.T) {
desc: "success AM",
input: PostableApiReceiver{
Receiver: config.Receiver{
Name: "foo",
EmailConfigs: []*config.EmailConfig{{}},
Name: "foo",
EmailConfigs: []*config.EmailConfig{{
To: "[email protected]",
HTML: config.DefaultEmailConfig.HTML,
Headers: map[string]string{},
}},
},
},
},
Expand All @@ -42,8 +46,12 @@ func Test_ApiReceiver_Marshaling(t *testing.T) {
desc: "failure mixed",
input: PostableApiReceiver{
Receiver: config.Receiver{
Name: "foo",
EmailConfigs: []*config.EmailConfig{{}},
Name: "foo",
EmailConfigs: []*config.EmailConfig{{
To: "[email protected]",
HTML: config.DefaultEmailConfig.HTML,
Headers: map[string]string{},
}},
},
PostableGrafanaReceivers: PostableGrafanaReceivers{
GrafanaManagedReceivers: []*PostableGrafanaReceiver{{}},
Expand Down Expand Up @@ -88,8 +96,12 @@ func Test_APIReceiverType(t *testing.T) {
desc: "am",
input: PostableApiReceiver{
Receiver: config.Receiver{
Name: "foo",
EmailConfigs: []*config.EmailConfig{{}},
Name: "foo",
EmailConfigs: []*config.EmailConfig{{
To: "[email protected]",
HTML: config.DefaultEmailConfig.HTML,
Headers: map[string]string{},
}},
},
},
expected: AlertmanagerReceiverType,
Expand Down Expand Up @@ -140,6 +152,7 @@ func Test_AllReceivers(t *testing.T) {
}

func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
defaultGlobalConfig := config.DefaultGlobalConfig()
for _, tc := range []struct {
desc string
input PostableApiAlertingConfig
Expand All @@ -149,6 +162,7 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
desc: "success am",
input: PostableApiAlertingConfig{
Config: Config{
Global: &defaultGlobalConfig,
Route: &Route{
Receiver: "am",
Routes: []*Route{
Expand All @@ -161,8 +175,12 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
Receivers: []*PostableApiReceiver{
{
Receiver: config.Receiver{
Name: "am",
EmailConfigs: []*config.EmailConfig{{}},
Name: "am",
EmailConfigs: []*config.EmailConfig{{
To: "[email protected]",
HTML: config.DefaultEmailConfig.HTML,
Headers: map[string]string{},
}},
},
},
},
Expand All @@ -172,6 +190,7 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
desc: "success graf",
input: PostableApiAlertingConfig{
Config: Config{
Global: &defaultGlobalConfig,
Route: &Route{
Receiver: "graf",
Routes: []*Route{
Expand All @@ -197,6 +216,7 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
desc: "failure undefined am receiver",
input: PostableApiAlertingConfig{
Config: Config{
Global: &defaultGlobalConfig,
Route: &Route{
Receiver: "am",
Routes: []*Route{
Expand All @@ -209,8 +229,12 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
Receivers: []*PostableApiReceiver{
{
Receiver: config.Receiver{
Name: "am",
EmailConfigs: []*config.EmailConfig{{}},
Name: "am",
EmailConfigs: []*config.EmailConfig{{
To: "[email protected]",
HTML: config.DefaultEmailConfig.HTML,
Headers: map[string]string{},
}},
},
},
},
Expand All @@ -221,6 +245,7 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
desc: "failure undefined graf receiver",
input: PostableApiAlertingConfig{
Config: Config{
Global: &defaultGlobalConfig,
Route: &Route{
Receiver: "graf",
Routes: []*Route{
Expand Down Expand Up @@ -263,6 +288,7 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
desc: "failure graf no default receiver",
input: PostableApiAlertingConfig{
Config: Config{
Global: &defaultGlobalConfig,
Route: &Route{
Routes: []*Route{
{
Expand All @@ -288,6 +314,7 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
desc: "failure graf root route with matchers",
input: PostableApiAlertingConfig{
Config: Config{
Global: &defaultGlobalConfig,
Route: &Route{
Receiver: "graf",
Routes: []*Route{
Expand Down Expand Up @@ -315,6 +342,7 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
desc: "failure graf nested route duplicate group by labels",
input: PostableApiAlertingConfig{
Config: Config{
Global: &defaultGlobalConfig,
Route: &Route{
Receiver: "graf",
Routes: []*Route{
Expand Down Expand Up @@ -342,6 +370,7 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
desc: "success undefined am receiver in autogenerated route is ignored",
input: PostableApiAlertingConfig{
Config: Config{
Global: &defaultGlobalConfig,
Route: &Route{
Receiver: "am",
Routes: []*Route{
Expand All @@ -365,8 +394,12 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
Receivers: []*PostableApiReceiver{
{
Receiver: config.Receiver{
Name: "am",
EmailConfigs: []*config.EmailConfig{{}},
Name: "am",
EmailConfigs: []*config.EmailConfig{{
To: "[email protected]",
HTML: config.DefaultEmailConfig.HTML,
Headers: map[string]string{},
}},
},
},
},
Expand All @@ -377,6 +410,7 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
desc: "success undefined graf receiver in autogenerated route is ignored",
input: PostableApiAlertingConfig{
Config: Config{
Global: &defaultGlobalConfig,
Route: &Route{
Receiver: "graf",
Routes: []*Route{
Expand Down Expand Up @@ -411,7 +445,7 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
err: false,
},
} {
t.Run(tc.desc, func(t *testing.T) {
t.Run(tc.desc+" (json)", func(t *testing.T) {
encoded, err := json.Marshal(tc.input)
require.Nil(t, err)

Expand All @@ -425,6 +459,21 @@ func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
require.Equal(t, tc.input, out)
}
})

t.Run(tc.desc+" (yaml)", func(t *testing.T) {
encoded, err := yaml.Marshal(tc.input)
require.Nil(t, err)

var out PostableApiAlertingConfig
err = yaml.Unmarshal(encoded, &out)

if tc.err {
require.Error(t, err)
} else {
require.Nil(t, err)
require.Equal(t, tc.input, out)
}
})
}
}

Expand Down