diff --git a/format.go b/format.go new file mode 100644 index 0000000..386e1a6 --- /dev/null +++ b/format.go @@ -0,0 +1,73 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "encoding/json" + "log" + "text/template" + + promtmpl "github.com/prometheus/alertmanager/template" +) + +type Formatter struct { + MsgTemplate *template.Template + MsgOnce bool +} + +func NewFormatter(config *Config) (*Formatter, error) { + tmpl, err := template.New("msg").Parse(config.MsgTemplate) + if err != nil { + return nil, err + } + return &Formatter{ + MsgTemplate: tmpl, + MsgOnce: config.MsgOnce, + }, nil +} + +func (f *Formatter) FormatMsg(ircChannel string, data interface{}) string { + output := bytes.Buffer{} + var msg string + if err := f.MsgTemplate.Execute(&output, data); err != nil { + msg_bytes, _ := json.Marshal(data) + msg = string(msg_bytes) + log.Printf("Could not apply msg template on alert (%s): %s", + err, msg) + log.Printf("Sending raw alert") + alertHandlingErrors.WithLabelValues(ircChannel, "format_msg").Inc() + } else { + msg = output.String() + } + return msg +} + +func (f *Formatter) GetMsgsFromAlertMessage(ircChannel string, + data *promtmpl.Data) []AlertMsg { + msgs := []AlertMsg{} + if f.MsgOnce { + msg := f.FormatMsg(ircChannel, data) + msgs = append(msgs, + AlertMsg{Channel: ircChannel, Alert: msg}) + } else { + for _, alert := range data.Alerts { + msg := f.FormatMsg(ircChannel, alert) + msgs = append(msgs, + AlertMsg{Channel: ircChannel, Alert: msg}) + } + } + return msgs +} diff --git a/format_test.go b/format_test.go new file mode 100644 index 0000000..129535d --- /dev/null +++ b/format_test.go @@ -0,0 +1,84 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + + promtmpl "github.com/prometheus/alertmanager/template" +) + +func TestTemplateErrorsCreateRawAlertMsg(t *testing.T) { + testingConfig := Config{MsgTemplate: "Bogus template {{ nil }}"} + formatter, _ := NewFormatter(&testingConfig) + + expectedAlertMsgs := []AlertMsg{ + AlertMsg{ + Channel: "#somechannel", + Alert: `{"status":"resolved","labels":{"alertname":"airDown","instance":"instance1:3456","job":"air","service":"prometheus","severity":"ticket","zone":"global"},"annotations":{"DESCRIPTION":"service /prometheus has irc gateway down on instance1","SUMMARY":"service /prometheus air down on instance1"},"startsAt":"2017-05-15T13:49:37.834Z","endsAt":"2017-05-15T13:50:37.835Z","generatorURL":"https://prometheus.example.com/prometheus/...","fingerprint":"66214a361160fb6f"}`, + }, + AlertMsg{ + Channel: "#somechannel", + Alert: `{"status":"resolved","labels":{"alertname":"airDown","instance":"instance2:7890","job":"air","service":"prometheus","severity":"ticket","zone":"global"},"annotations":{"DESCRIPTION":"service /prometheus has irc gateway down on instance2","SUMMARY":"service /prometheus air down on instance2"},"startsAt":"2017-05-15T11:47:37.834Z","endsAt":"2017-05-15T11:48:37.834Z","generatorURL":"https://prometheus.example.com/prometheus/...","fingerprint":"25a874c99325d1ce"}`, + }, + } + + var alertMessage = promtmpl.Data{} + if err := json.Unmarshal([]byte(testdataSimpleAlertJson), &alertMessage); err != nil { + t.Fatal(fmt.Sprintf("Could not unmarshal %s", testdataSimpleAlertJson)) + } + + alertMsgs := formatter.GetMsgsFromAlertMessage("#somechannel", &alertMessage) + + if !reflect.DeepEqual(expectedAlertMsgs, alertMsgs) { + t.Error(fmt.Sprintf( + "Unexpected alert msg.\nExpected: %s\nActual: %s", + expectedAlertMsgs, alertMsgs)) + + } + +} + +func TestAlertsDispatchedOnce(t *testing.T) { + testingConfig := Config{ + MsgTemplate: "Alert {{ .GroupLabels.alertname }} is {{ .Status }}", + MsgOnce: true, + } + formatter, _ := NewFormatter(&testingConfig) + + expectedAlertMsgs := []AlertMsg{ + AlertMsg{ + Channel: "#somechannel", + Alert: "Alert airDown is resolved", + }, + } + + var alertMessage = promtmpl.Data{} + if err := json.Unmarshal([]byte(testdataSimpleAlertJson), &alertMessage); err != nil { + t.Fatal(fmt.Sprintf("Could not unmarshal %s", testdataSimpleAlertJson)) + } + + alertMsgs := formatter.GetMsgsFromAlertMessage("#somechannel", &alertMessage) + + if !reflect.DeepEqual(expectedAlertMsgs, alertMsgs) { + t.Error(fmt.Sprintf( + "Unexpected alert msg.\nExpected: %s\nActual: %s", + expectedAlertMsgs, alertMsgs)) + + } +} diff --git a/http.go b/http.go index bf64093..9a8bedb 100644 --- a/http.go +++ b/http.go @@ -15,7 +15,6 @@ package main import ( - "bytes" "encoding/json" "io" "io/ioutil" @@ -23,7 +22,6 @@ import ( "net/http" "strconv" "strings" - "text/template" "github.com/gorilla/mux" promtmpl "github.com/prometheus/alertmanager/template" @@ -56,8 +54,7 @@ type HTTPServer struct { StoppedRunning chan bool Addr string Port int - MsgTemplate *template.Template - MsgOnce bool + formatter *Formatter AlertMsgs chan AlertMsg httpListener HTTPListener } @@ -69,7 +66,7 @@ func NewHTTPServer(config *Config, alertMsgs chan AlertMsg) ( func NewHTTPServerForTesting(config *Config, alertMsgs chan AlertMsg, httpListener HTTPListener) (*HTTPServer, error) { - tmpl, err := template.New("msg").Parse(config.MsgTemplate) + formatter, err := NewFormatter(config) if err != nil { return nil, err } @@ -77,8 +74,7 @@ func NewHTTPServerForTesting(config *Config, alertMsgs chan AlertMsg, StoppedRunning: make(chan bool), Addr: config.HTTPHost, Port: config.HTTPPort, - MsgTemplate: tmpl, - MsgOnce: config.MsgOnce, + formatter: formatter, AlertMsgs: alertMsgs, httpListener: httpListener, } @@ -86,39 +82,6 @@ func NewHTTPServerForTesting(config *Config, alertMsgs chan AlertMsg, return server, nil } -func (server *HTTPServer) FormatMsg(ircChannel string, data interface{}) string { - output := bytes.Buffer{} - var msg string - if err := server.MsgTemplate.Execute(&output, data); err != nil { - msg_bytes, _ := json.Marshal(data) - msg = string(msg_bytes) - log.Printf("Could not apply msg template on alert (%s): %s", - err, msg) - log.Printf("Sending raw alert") - alertHandlingErrors.WithLabelValues(ircChannel, "format_msg").Inc() - } else { - msg = output.String() - } - return msg -} - -func (server *HTTPServer) GetMsgsFromAlertMessage(ircChannel string, - data *promtmpl.Data) []AlertMsg { - msgs := []AlertMsg{} - if server.MsgOnce { - msg := server.FormatMsg(ircChannel, data) - msgs = append(msgs, - AlertMsg{Channel: ircChannel, Alert: msg}) - } else { - for _, alert := range data.Alerts { - msg := server.FormatMsg(ircChannel, alert) - msgs = append(msgs, - AlertMsg{Channel: ircChannel, Alert: msg}) - } - } - return msgs -} - func (server *HTTPServer) RelayAlert(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) ircChannel := "#" + vars["IRCChannel"] @@ -143,7 +106,7 @@ func (server *HTTPServer) RelayAlert(w http.ResponseWriter, r *http.Request) { return } handledAlertGroups.WithLabelValues(ircChannel).Inc() - for _, alertMsg := range server.GetMsgsFromAlertMessage( + for _, alertMsg := range server.formatter.GetMsgsFromAlertMessage( ircChannel, &alertMessage) { select { case server.AlertMsgs <- alertMsg: diff --git a/http_test.go b/http_test.go index a7a76c3..4ece468 100644 --- a/http_test.go +++ b/http_test.go @@ -116,39 +116,6 @@ func TestAlertsDispatched(t *testing.T) { } } -func TestAlertsDispatchedOnce(t *testing.T) { - listener := NewFakeHTTPListener() - testingConfig := MakeHTTPTestingConfig() - testingConfig.MsgOnce = true - testingConfig.MsgTemplate = "Alert {{ .GroupLabels.alertname }} is {{ .Status }}" - - expectedAlertMsgs := []AlertMsg{ - AlertMsg{ - Channel: "#somechannel", - Alert: "Alert airDown is resolved", - }, - } - expectedStatusCode := 200 - - response := RunHTTPTest( - t, testdataSimpleAlertJson, "/somechannel", - testingConfig, listener) - - if expectedStatusCode != response.StatusCode { - t.Error(fmt.Sprintf("Expected %d status in response, got %d", - expectedStatusCode, response.StatusCode)) - } - - for _, expectedAlertMsg := range expectedAlertMsgs { - alertMsg := <-listener.AlertMsgs - if !reflect.DeepEqual(expectedAlertMsg, alertMsg) { - t.Error(fmt.Sprintf( - "Unexpected alert msg.\nExpected: %s\nActual: %s", - expectedAlertMsg, alertMsg)) - } - } -} - func TestRootReturnsError(t *testing.T) { listener := NewFakeHTTPListener() testingConfig := MakeHTTPTestingConfig() @@ -180,39 +147,3 @@ func TestInvalidDataReturnsError(t *testing.T) { expectedStatusCode, response.StatusCode)) } } - -func TestTemplateErrorsCreateRawAlertMsg(t *testing.T) { - listener := NewFakeHTTPListener() - testingConfig := MakeHTTPTestingConfig() - testingConfig.MsgTemplate = "Bogus template {{ nil }}" - - expectedAlertMsgs := []AlertMsg{ - AlertMsg{ - Channel: "#somechannel", - Alert: `{"status":"resolved","labels":{"alertname":"airDown","instance":"instance1:3456","job":"air","service":"prometheus","severity":"ticket","zone":"global"},"annotations":{"DESCRIPTION":"service /prometheus has irc gateway down on instance1","SUMMARY":"service /prometheus air down on instance1"},"startsAt":"2017-05-15T13:49:37.834Z","endsAt":"2017-05-15T13:50:37.835Z","generatorURL":"https://prometheus.example.com/prometheus/...","fingerprint":"66214a361160fb6f"}`, - }, - AlertMsg{ - Channel: "#somechannel", - Alert: `{"status":"resolved","labels":{"alertname":"airDown","instance":"instance2:7890","job":"air","service":"prometheus","severity":"ticket","zone":"global"},"annotations":{"DESCRIPTION":"service /prometheus has irc gateway down on instance2","SUMMARY":"service /prometheus air down on instance2"},"startsAt":"2017-05-15T11:47:37.834Z","endsAt":"2017-05-15T11:48:37.834Z","generatorURL":"https://prometheus.example.com/prometheus/...","fingerprint":"25a874c99325d1ce"}`, - }, - } - expectedStatusCode := 200 - - response := RunHTTPTest( - t, testdataSimpleAlertJson, "/somechannel", - testingConfig, listener) - - if expectedStatusCode != response.StatusCode { - t.Error(fmt.Sprintf("Expected %d status in response, got %d", - expectedStatusCode, response.StatusCode)) - } - - for _, expectedAlertMsg := range expectedAlertMsgs { - alertMsg := <-listener.AlertMsgs - if !reflect.DeepEqual(expectedAlertMsg, alertMsg) { - t.Error(fmt.Sprintf( - "Unexpected alert msg.\nExpected: %s\nActual: %s", - expectedAlertMsg, alertMsg)) - } - } -}