diff -Nru alertmanager-irc-relay-0.1.0/.circleci/config.yml alertmanager-irc-relay-0.2.0/.circleci/config.yml --- alertmanager-irc-relay-0.1.0/.circleci/config.yml 2020-03-05 11:58:39.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/.circleci/config.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -version: 2 -jobs: - test-1.9: - docker: - - image: circleci/golang:1.9 - working_directory: /go/src/github.com/google/alertmanager-irc-relay - steps: - - checkout - - run: go get -v -t -d ./... - - run: go test -v ./... - test-1.10: - docker: - - image: circleci/golang:1.10 - working_directory: /go/src/github.com/google/alertmanager-irc-relay - steps: - - checkout - - run: go get -v -t -d ./... - - run: go test -v ./... -workflows: - version: 2 - test: - jobs: - - test-1.9 - - test-1.10 diff -Nru alertmanager-irc-relay-0.1.0/config.go alertmanager-irc-relay-0.2.0/config.go --- alertmanager-irc-relay-0.1.0/config.go 2020-03-05 11:58:39.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/config.go 2020-11-10 10:01:49.000000000 +0000 @@ -17,6 +17,7 @@ import ( "gopkg.in/yaml.v2" "io/ioutil" + "os" ) const ( @@ -37,6 +38,7 @@ IRCRealName string `yaml:"irc_realname"` IRCHost string `yaml:"irc_host"` IRCPort int `yaml:"irc_port"` + IRCHostPass string `yaml:"irc_host_password"` IRCUseSSL bool `yaml:"irc_use_ssl"` IRCVerifySSL bool `yaml:"irc_verify_ssl"` IRCChannels []IRCChannel `yaml:"irc_channels"` @@ -54,6 +56,7 @@ IRCRealName: "Alertmanager IRC Relay", IRCHost: "example.com", IRCPort: 7000, + IRCHostPass: "", IRCUseSSL: true, IRCVerifySSL: true, IRCChannels: []IRCChannel{}, @@ -66,6 +69,7 @@ if err != nil { return nil, err } + data = []byte(os.ExpandEnv(string(data))) if err := yaml.Unmarshal(data, config); err != nil { return nil, err } diff -Nru alertmanager-irc-relay-0.1.0/config_test.go alertmanager-irc-relay-0.2.0/config_test.go --- alertmanager-irc-relay-0.1.0/config_test.go 2020-03-05 11:58:39.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/config_test.go 2020-11-10 10:01:49.000000000 +0000 @@ -40,6 +40,7 @@ IRCNick: "foo", IRCHost: "irc.example.com", IRCPort: 1234, + IRCHostPass: "hostsecret", IRCUseSSL: true, IRCChannels: []IRCChannel{IRCChannel{Name: "#foobar"}}, MsgTemplate: defaultMsgTemplate, @@ -79,6 +80,35 @@ } } +func TestLoadWithEnvironmentVariables(t *testing.T) { + expectedNickPass := "mynickpass" + + os.Setenv("NICKSERV_PASSWORD", expectedNickPass) + defer os.Clearenv() + + tmpfile, err := ioutil.TempFile("", "airtestenvvarconfig") + if err != nil { + t.Errorf("Could not create tmpfile for testing: %s", err) + } + defer os.Remove(tmpfile.Name()) + + msgOnceConfigData := []byte("irc_nickname_password: $NICKSERV_PASSWORD") + if _, err := tmpfile.Write(msgOnceConfigData); err != nil { + t.Errorf("Could not write test data in tmpfile: %s", err) + } + tmpfile.Close() + + config, err := LoadConfig(tmpfile.Name()) + if config == nil { + t.Errorf("Expected a config, got: %s", err) + } + + if config.IRCNickPass != expectedNickPass { + t.Errorf("Loaded unexpected value: %s (expected: %s)", + config.IRCNickPass, expectedNickPass) + } +} + func TestLoadBadFile(t *testing.T) { tmpfile, err := ioutil.TempFile("", "airtestbadfile") if err != nil { diff -Nru alertmanager-irc-relay-0.1.0/debian/changelog alertmanager-irc-relay-0.2.0/debian/changelog --- alertmanager-irc-relay-0.1.0/debian/changelog 2020-11-11 22:03:32.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/debian/changelog 2020-11-17 09:47:57.000000000 +0000 @@ -1,8 +1,8 @@ -alertmanager-irc-relay (0.1.0-3build1) hirsute; urgency=medium +alertmanager-irc-relay (0.2.0-1) unstable; urgency=medium - * No-change rebuild using new golang + * New upstream release - -- Steve Langasek Wed, 11 Nov 2020 22:03:32 +0000 + -- Filippo Giunchedi Tue, 17 Nov 2020 10:47:57 +0100 alertmanager-irc-relay (0.1.0-3) unstable; urgency=medium diff -Nru alertmanager-irc-relay-0.1.0/debian/control alertmanager-irc-relay-0.2.0/debian/control --- alertmanager-irc-relay-0.1.0/debian/control 2020-11-11 22:03:32.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/debian/control 2020-11-17 09:47:43.000000000 +0000 @@ -1,8 +1,7 @@ Source: alertmanager-irc-relay Section: devel Priority: optional -Maintainer: Ubuntu Developers -XSBC-Original-Maintainer: Debian Go Packaging Team +Maintainer: Debian Go Packaging Team Uploaders: Filippo Giunchedi Build-Depends: debhelper (>= 11), dh-golang, diff -Nru alertmanager-irc-relay-0.1.0/format.go alertmanager-irc-relay-0.2.0/format.go --- alertmanager-irc-relay-0.1.0/format.go 1970-01-01 00:00:00.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/format.go 2020-11-10 10:01:49.000000000 +0000 @@ -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 ( + "bytes" + "encoding/json" + "log" + "net/url" + "strings" + "text/template" + + promtmpl "github.com/prometheus/alertmanager/template" +) + +type Formatter struct { + MsgTemplate *template.Template + MsgOnce bool +} + +func NewFormatter(config *Config) (*Formatter, error) { + funcMap := template.FuncMap{ + "ToUpper": strings.ToUpper, + "ToLower": strings.ToLower, + "Join": strings.Join, + + "QueryEscape": url.QueryEscape, + "PathEscape": url.PathEscape, + } + + tmpl, err := template.New("msg").Funcs(funcMap).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 -Nru alertmanager-irc-relay-0.1.0/format_test.go alertmanager-irc-relay-0.2.0/format_test.go --- alertmanager-irc-relay-0.1.0/format_test.go 1970-01-01 00:00:00.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/format_test.go 2020-11-10 10:01:49.000000000 +0000 @@ -0,0 +1,111 @@ +// 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 CreateFormatterAndCheckOutput(t *testing.T, c *Config, expected []AlertMsg) { + f, _ := NewFormatter(c) + + var alertMessage = promtmpl.Data{} + if err := json.Unmarshal([]byte(testdataSimpleAlertJson), &alertMessage); err != nil { + t.Fatal(fmt.Sprintf("Could not unmarshal %s", testdataSimpleAlertJson)) + } + + alertMsgs := f.GetMsgsFromAlertMessage("#somechannel", &alertMessage) + + if !reflect.DeepEqual(expected, alertMsgs) { + t.Error(fmt.Sprintf( + "Unexpected alert msg.\nExpected: %s\nActual: %s", + expected, alertMsgs)) + + } + +} + +func TestTemplateErrorsCreateRawAlertMsg(t *testing.T) { + testingConfig := Config{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"}`, + }, + } + + CreateFormatterAndCheckOutput(t, &testingConfig, expectedAlertMsgs) +} + +func TestAlertsDispatchedOnce(t *testing.T) { + testingConfig := Config{ + MsgTemplate: "Alert {{ .GroupLabels.alertname }} is {{ .Status }}", + MsgOnce: true, + } + + expectedAlertMsgs := []AlertMsg{ + AlertMsg{ + Channel: "#somechannel", + Alert: "Alert airDown is resolved", + }, + } + + CreateFormatterAndCheckOutput(t, &testingConfig, expectedAlertMsgs) +} + +func TestStringsFunctions(t *testing.T) { + testingConfig := Config{ + MsgTemplate: "Alert {{ .GroupLabels.alertname | ToUpper }} is {{ .Status }}", + MsgOnce: true, + } + + expectedAlertMsgs := []AlertMsg{ + AlertMsg{ + Channel: "#somechannel", + Alert: "Alert AIRDOWN is resolved", + }, + } + + CreateFormatterAndCheckOutput(t, &testingConfig, expectedAlertMsgs) +} + +func TestUrlFunctions(t *testing.T) { + testingConfig := Config{ + MsgTemplate: "{{ .Annotations.SUMMARY | PathEscape }}", + } + + expectedAlertMsgs := []AlertMsg{ + AlertMsg{ + Channel: "#somechannel", + Alert: "service%20%2Fprometheus%20air%20down%20on%20instance1", + }, + AlertMsg{ + Channel: "#somechannel", + Alert: "service%20%2Fprometheus%20air%20down%20on%20instance2", + }, + } + + CreateFormatterAndCheckOutput(t, &testingConfig, expectedAlertMsgs) +} diff -Nru alertmanager-irc-relay-0.1.0/.github/workflows/test.yml alertmanager-irc-relay-0.2.0/.github/workflows/test.yml --- alertmanager-irc-relay-0.1.0/.github/workflows/test.yml 1970-01-01 00:00:00.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/.github/workflows/test.yml 2020-11-10 10:01:49.000000000 +0000 @@ -0,0 +1,18 @@ +on: [push, pull_request] +name: Test +jobs: + test: + strategy: + matrix: + go-version: [1.11.x, 1.14.x, 1.15.x] + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: Test + run: go test -v ./... diff -Nru alertmanager-irc-relay-0.1.0/http.go alertmanager-irc-relay-0.2.0/http.go --- alertmanager-irc-relay-0.1.0/http.go 2020-03-05 11:58:39.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/http.go 2020-11-10 10:01:49.000000000 +0000 @@ -15,7 +15,6 @@ package main import ( - "bytes" "encoding/json" "io" "io/ioutil" @@ -23,7 +22,6 @@ "net/http" "strconv" "strings" - "text/template" "github.com/gorilla/mux" promtmpl "github.com/prometheus/alertmanager/template" @@ -56,8 +54,7 @@ StoppedRunning chan bool Addr string Port int - MsgTemplate *template.Template - MsgOnce bool + formatter *Formatter AlertMsgs chan AlertMsg httpListener HTTPListener } @@ -69,7 +66,7 @@ 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 @@ 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 @@ 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 @@ return } handledAlertGroups.WithLabelValues(ircChannel).Inc() - for _, alertMsg := range server.GetMsgsFromAlertMessage( + for _, alertMsg := range server.formatter.GetMsgsFromAlertMessage( ircChannel, &alertMessage) { select { case server.AlertMsgs <- alertMsg: diff -Nru alertmanager-irc-relay-0.1.0/http_test.go alertmanager-irc-relay-0.2.0/http_test.go --- alertmanager-irc-relay-0.1.0/http_test.go 2020-03-05 11:58:39.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/http_test.go 2020-11-10 10:01:49.000000000 +0000 @@ -116,39 +116,6 @@ } } -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 @@ 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)) - } - } -} diff -Nru alertmanager-irc-relay-0.1.0/irc.go alertmanager-irc-relay-0.2.0/irc.go --- alertmanager-irc-relay-0.1.0/irc.go 2020-03-05 11:58:39.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/irc.go 2020-11-10 10:01:49.000000000 +0000 @@ -95,6 +95,7 @@ ircConfig.Me.Name = config.IRCRealName ircConfig.Server = strings.Join( []string{config.IRCHost, strconv.Itoa(config.IRCPort)}, ":") + ircConfig.Pass = config.IRCHostPass ircConfig.SSL = config.IRCUseSSL ircConfig.SSLConfig = &tls.Config{ ServerName: config.IRCHost, diff -Nru alertmanager-irc-relay-0.1.0/irc_test.go alertmanager-irc-relay-0.2.0/irc_test.go --- alertmanager-irc-relay-0.1.0/irc_test.go 2020-03-05 11:58:39.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/irc_test.go 2020-11-10 10:01:49.000000000 +0000 @@ -267,6 +267,46 @@ } } +func TestServerPassword(t *testing.T) { + server, port := makeTestServer(t) + config := makeTestIRCConfig(port) + config.IRCHostPass = "hostsecret" + notifier, _ := makeTestNotifier(t, config) + + var testStep sync.WaitGroup + + joinHandler := func(conn *bufio.ReadWriter, line *irc.Line) error { + // #baz is configured as the last channel to pre-join + if line.Args[0] == "#baz" { + testStep.Done() + } + return nil + } + server.SetHandler("JOIN", joinHandler) + + testStep.Add(1) + go notifier.Run() + + testStep.Wait() + + notifier.StopRunning <- true + server.Stop() + + expectedCommands := []string{ + "PASS hostsecret", + "NICK foo", + "USER foo 12 * :", + "JOIN #foo", + "JOIN #bar", + "JOIN #baz", + "QUIT :see ya", + } + + if !reflect.DeepEqual(expectedCommands, server.Log) { + t.Error("Did not send IRC server password") + } +} + func TestSendAlertOnPreJoinedChannel(t *testing.T) { server, port := makeTestServer(t) config := makeTestIRCConfig(port) diff -Nru alertmanager-irc-relay-0.1.0/README.md alertmanager-irc-relay-0.2.0/README.md --- alertmanager-irc-relay-0.1.0/README.md 2020-03-05 11:58:39.000000000 +0000 +++ alertmanager-irc-relay-0.2.0/README.md 2020-11-10 10:01:49.000000000 +0000 @@ -25,6 +25,8 @@ # Note: SSL is enabled by default, use "irc_use_ssl: no" to disable. irc_host: irc.example.com irc_port: 7000 +# Optionally set the server password +irc_host_password: myserver_password # Use this IRC nickname. irc_nickname: myalertbot @@ -69,6 +71,20 @@ $ alertmanager-irc-relay --config /path/to/your/config/file ``` +The configuration file can reference environment variables. It is then possible +to specify certain parameters directly when running the bot: +``` +$ cat /path/to/your/config/file +... +http_port: $MY_SERVICE_PORT +... +irc_nickname_password: $NICKSERV_PASSWORD +... +$ export MY_SERVICE_PORT=8000 NICKSERV_PASSWORD=mynickserv_key +$ alertmanager-irc-relay --config /path/to/your/config/file +``` + + ### Prometheus configuration Prometheus can be configured following the official