Skip to content

Commit

Permalink
Add YAML unmarshalers for Alertmanager configuration structs (#186)
Browse files Browse the repository at this point in the history
Add YAML unmarshalers for Alertmanager configuration struct
  • Loading branch information
santihernandezc authored May 14, 2024
1 parent bb4f4f4 commit b986cf0
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 38 deletions.
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

0 comments on commit b986cf0

Please sign in to comment.