diff --git a/CHANGELOG.md b/CHANGELOG.md index d4cae8ea..40ff9c80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.20.0-rc.0 / 2019-11-27 +## 0.20.0 / 2019-12-11 * [CHANGE] Check that at least one silence matcher matches a non-empty string. #2081 * [ENHANCEMENT] [pagerduty] Check that PagerDuty keys aren't empty. #2085 @@ -11,7 +11,6 @@ * [BUGFIX] Don't garbage-collect alerts from the store. #2040 * [BUGFIX] [ui] Disable the grammarly plugin on all textareas. #2061 * [BUGFIX] [config] Forbid nil regexp matchers. #2083 -* [BUGFIX] [slack] Retry 429 errors. #2112 * [BUGFIX] [ui] Fix Silences UI when several filters are applied. #2075 Contributors: diff --git a/VERSION b/VERSION index ad6e251d..5a03fb73 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.20.0-rc.0 +0.20.0 diff --git a/client/client_test.go b/client/client_test.go index 7455769d..5a3c15f4 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -90,9 +90,18 @@ func TestAPI(t *testing.T) { client := &fakeAPIClient{T: t, ch: make(chan fakeAPIResponse, 1)} now := time.Now() + u, err := url.Parse("http://example.com") + if err != nil { + t.Errorf("unexpected error: %v", err) + } statusData := &ServerStatus{ - ConfigYAML: "{}", - ConfigJSON: &config.Config{}, + ConfigYAML: "{}", + ConfigJSON: &config.Config{ + Global: &config.GlobalConfig{ + PagerdutyURL: &config.URL{URL: u}, + SMTPSmarthost: config.HostPort{Host: "localhost", Port: "25"}, + }, + }, VersionInfo: map[string]string{"version": "v1"}, Uptime: now, ClusterStatus: &ClusterStatus{Peers: []PeerStatus{}}, diff --git a/config/config.go b/config/config.go index 98991c26..5294e4da 100644 --- a/config/config.go +++ b/config/config.go @@ -105,6 +105,20 @@ func (u URL) MarshalJSON() ([]byte, error) { return nil, nil } +// UnmarshalJSON implements the json.Marshaler interface for URL. +func (u *URL) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + urlp, err := parseURL(s) + if err != nil { + return err + } + u.URL = urlp.URL + return nil +} + // SecretURL is a URL that must not be revealed on marshaling. type SecretURL URL @@ -137,6 +151,18 @@ func (s SecretURL) MarshalJSON() ([]byte, error) { return json.Marshal(secretToken) } +// UnmarshalJSON implements the json.Marshaler interface for SecretURL. +func (s *SecretURL) UnmarshalJSON(data []byte) error { + // In order to deserialize a previously serialized configuration (eg from + // the Alertmanager API with amtool), `` needs to be treated + // specially, as it isn't a valid URL. + if string(data) == secretToken || string(data) == secretTokenJSON { + s.URL = &url.URL{} + return nil + } + return json.Unmarshal(data, (*URL)(s)) +} + // Load parses the YAML input s into a Config. func Load(s string) (*Config, error) { cfg := &Config{} @@ -490,6 +516,28 @@ func (hp *HostPort) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } +// UnmarshalJSON implements the json.Unmarshaler interface for HostPort. +func (hp *HostPort) UnmarshalJSON(data []byte) error { + var ( + s string + err error + ) + if err = json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + hp.Host, hp.Port, err = net.SplitHostPort(s) + if err != nil { + return err + } + if hp.Port == "" { + return errors.Errorf("address %q: port cannot be empty", s) + } + return nil +} + // MarshalYAML implements the yaml.Marshaler interface for HostPort. func (hp HostPort) MarshalYAML() (interface{}, error) { return hp.String(), nil @@ -729,3 +777,26 @@ func (re Regexp) MarshalYAML() (interface{}, error) { } return nil, nil } + +// UnmarshalJSON implements the json.Marshaler interface for Regexp +func (re *Regexp) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + regex, err := regexp.Compile("^(?:" + s + ")$") + if err != nil { + return err + } + re.Regexp = regex + re.original = s + return nil +} + +// MarshalJSON implements the json.Marshaler interface for Regexp. +func (re Regexp) MarshalJSON() ([]byte, error) { + if re.original != "" { + return json.Marshal(re.original) + } + return nil, nil +} diff --git a/config/config_test.go b/config/config_test.go index c5103986..0305ab2b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -367,13 +367,27 @@ func TestMarshalSecretURL(t *testing.T) { } u := &SecretURL{urlp} - c, err := yaml.Marshal(u) + c, err := json.Marshal(u) + if err != nil { + t.Fatal(err) + } + // u003c -> "<" + // u003e -> ">" + require.Equal(t, "\"\\u003csecret\\u003e\"", string(c), "SecretURL not properly elided in JSON.") + // Check that the marshaled data can be unmarshaled again. + out := &SecretURL{} + err = json.Unmarshal(c, out) + if err != nil { + t.Fatal(err) + } + + c, err = yaml.Marshal(u) if err != nil { t.Fatal(err) } require.Equal(t, "\n", string(c), "SecretURL not properly elided in YAML.") // Check that the marshaled data can be unmarshaled again. - out := &SecretURL{} + out = &SecretURL{} err = yaml.Unmarshal(c, &out) if err != nil { t.Fatal(err) @@ -384,7 +398,13 @@ func TestUnmarshalSecretURL(t *testing.T) { b := []byte(`"http://example.com/se cret"`) var u SecretURL - err := yaml.Unmarshal(b, &u) + err := json.Unmarshal(b, &u) + if err != nil { + t.Fatal(err) + } + require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshalled in JSON.") + + err = yaml.Unmarshal(b, &u) if err != nil { t.Fatal(err) } @@ -416,7 +436,13 @@ func TestUnmarshalURL(t *testing.T) { b := []byte(`"http://example.com/a b"`) var u URL - err := yaml.Unmarshal(b, &u) + err := json.Unmarshal(b, &u) + if err != nil { + t.Fatal(err) + } + require.Equal(t, "http://example.com/a%20b", u.String(), "URL not properly unmarshalled in JSON.") + + err = yaml.Unmarshal(b, &u) if err != nil { t.Fatal(err) } @@ -431,7 +457,12 @@ func TestUnmarshalInvalidURL(t *testing.T) { } { var u URL - err := yaml.Unmarshal(b, &u) + err := json.Unmarshal(b, &u) + if err == nil { + t.Errorf("Expected an error unmarshalling %q from JSON", string(b)) + } + + err = yaml.Unmarshal(b, &u) if err == nil { t.Errorf("Expected an error unmarshalling %q from YAML", string(b)) } @@ -443,10 +474,27 @@ func TestUnmarshalRelativeURL(t *testing.T) { b := []byte(`"/home"`) var u URL - err := yaml.Unmarshal(b, &u) + err := json.Unmarshal(b, &u) if err == nil { t.Errorf("Expected an error parsing URL") } + + err = yaml.Unmarshal(b, &u) + if err == nil { + t.Errorf("Expected an error parsing URL") + } +} + +func TestJSONUnmarshal(t *testing.T) { + c, err := LoadFile("testdata/conf.good.yml") + if err != nil { + t.Errorf("Error parsing %s: %s", "testdata/conf.good.yml", err) + } + + _, err = json.Marshal(c) + if err != nil { + t.Fatal("JSON Marshaling failed:", err) + } } func TestMarshalIdempotency(t *testing.T) { diff --git a/dispatch/route.go b/dispatch/route.go index 57899895..43c48c25 100644 --- a/dispatch/route.go +++ b/dispatch/route.go @@ -14,6 +14,7 @@ package dispatch import ( + "encoding/json" "fmt" "sort" "strings" @@ -181,3 +182,26 @@ func (ro *RouteOpts) String() string { return fmt.Sprintf("", ro.Receiver, labels, ro.GroupByAll, ro.GroupWait, ro.GroupInterval) } + +// MarshalJSON returns a JSON representation of the routing options. +func (ro *RouteOpts) MarshalJSON() ([]byte, error) { + v := struct { + Receiver string `json:"receiver"` + GroupBy model.LabelNames `json:"groupBy"` + GroupByAll bool `json:"groupByAll"` + GroupWait time.Duration `json:"groupWait"` + GroupInterval time.Duration `json:"groupInterval"` + RepeatInterval time.Duration `json:"repeatInterval"` + }{ + Receiver: ro.Receiver, + GroupByAll: ro.GroupByAll, + GroupWait: ro.GroupWait, + GroupInterval: ro.GroupInterval, + RepeatInterval: ro.RepeatInterval, + } + for ln := range ro.GroupBy { + v.GroupBy = append(v.GroupBy, ln) + } + + return json.Marshal(&v) +} diff --git a/notify/slack/slack.go b/notify/slack/slack.go index 8d083909..45a08719 100644 --- a/notify/slack/slack.go +++ b/notify/slack/slack.go @@ -49,7 +49,7 @@ func New(c *config.SlackConfig, t *template.Template, l log.Logger) (*Notifier, tmpl: t, logger: l, client: client, - retrier: ¬ify.Retrier{RetryCodes: []int{http.StatusTooManyRequests}}, + retrier: ¬ify.Retrier{}, }, nil } diff --git a/notify/slack/slack_test.go b/notify/slack/slack_test.go index b5ebe641..2b77e63e 100644 --- a/notify/slack/slack_test.go +++ b/notify/slack/slack_test.go @@ -15,7 +15,6 @@ package slack import ( "fmt" - "net/http" "testing" "github.com/go-kit/kit/log" @@ -36,8 +35,7 @@ func TestSlackRetry(t *testing.T) { ) require.NoError(t, err) - retryCodes := append(test.DefaultRetryCodes(), http.StatusTooManyRequests) - for statusCode, expected := range test.RetryTests(retryCodes) { + for statusCode, expected := range test.RetryTests(test.DefaultRetryCodes()) { actual, _ := notifier.retrier.Check(statusCode, nil) require.Equal(t, expected, actual, fmt.Sprintf("error on status %d", statusCode)) }