Skip to content

Commit

Permalink
feat: Add support for multiple chat rooms
Browse files Browse the repository at this point in the history
A new required query param (`room_name`) is introduced in `/create` POST call.
This is for routing alerts to different teams based on the alertmanager
config.

The `room_name` must be additionally configured in `config.toml`, which
has information about `notification_url` of the room.
  • Loading branch information
mr-karan committed Jan 11, 2019
1 parent ac0cb5a commit 9121985
Show file tree
Hide file tree
Showing 8 changed files with 57 additions and 39 deletions.
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@

`calert` is a lightweight binary to push [Alertmanager](https://github.com/prometheus/alertmanager) notifications to [Google Chat](http://chat.google.com) via webhook integration.


## Install

There are various ways of installing calert.

### Precompiled binaries

Precompiled binaries for released versions are available in the [*Releases* section](https://github.com/mr-karan/calert/releases/).
Precompiled binaries for released versions are available in the [_Releases_ section](https://github.com/mr-karan/calert/releases/).

### Compiling the binary

Expand All @@ -29,45 +28,46 @@ $ cp config.toml.sample config.toml
$ ./calert
```


## How to Use

`calert` uses Alertmanager [webhook receiver](https://prometheus.io/docs/alerting/configuration/#webhook_config) to receive alerts payload, and pushes this data to Google Chat [webhook](https://developers.google.com/hangouts/chat/how-tos/webhooks) endpoint.

- Download the binary and [sample config file](config.toml.sample) and place them in one folder.
- Download the binary and [sample config file](config.toml.sample) and place them in one folder.

```sh
mkdir calert-example && cd calert-example/
cp config.toml.sample config.toml # change the settings like hostname, address, google chat webhook url, timeouts etc in this file.
./calert.bin # this command starts a web server and is ready to receive events from alertmanager
```

- Set the webhook URL from Google Chat in `[app.notification_url]` section of `config.toml`. You can refer to the [official documentation](https://developers.google.com/hangouts/chat/quickstart/incoming-bot-python#step_1_register_the_incoming_webhook) for more details.
- Set the webhook URL from Google Chat in `[app.notification_url]` section of `config.toml`. You can refer to the [official documentation](https://developers.google.com/hangouts/chat/quickstart/incoming-bot-python#step_1_register_the_incoming_webhook) for more details.

- Configure Alertmanager config file (`alertmanager.yml`) and give the address of calert web-server. You can refer to the [official documentation](https://prometheus.io/docs/alerting/configuration/#webhook_config) for more details.
- Configure Alertmanager config file (`alertmanager.yml`) and give the address of calert web-server. You can refer to the [official documentation](https://prometheus.io/docs/alerting/configuration/#webhook_config) for more details.

You are now ready to send alerts to Google Chat!

![](images/gchat.png)

## Configuration

- You can configure the default template for notification and create your own. Create a [template](https://golang.org/pkg/text/template/) file, similar to [message.tmpl](message.tmpl) and set the path of this file in `[app.template_file]` section of `config.toml`

- Alertmanager has the ability of group similar alerts together and fire only one event, clubbing all the alerts data into one event. `calert` leverages this and sends all alerts in one message by looping over the alerts and passing data in the template. You can configure the rules for grouping the alerts in `alertmanager.yml` config. You can read more about it [here](https://github.com/prometheus/docs/blob/master/content/docs/alerting/alertmanager.md#grouping)
- You can configure the default template for notification and create your own. Create a [template](https://golang.org/pkg/text/template/) file, similar to [message.tmpl](message.tmpl) and set the path of this file in `[app.template_file]` section of `config.toml`

- Alertmanager has the ability of group similar alerts together and fire only one event, clubbing all the alerts data into one event. `calert` leverages this and sends all alerts in one message by looping over the alerts and passing data in the template. You can configure the rules for grouping the alerts in `alertmanager.yml` config. You can read more about it [here](https://github.com/prometheus/docs/blob/master/content/docs/alerting/alertmanager.md#grouping)

## API

- POST `/create` (Used to receive new alerts and push to Google Chat)
- POST `/create?room_name=<>` (Used to receive new alerts and push to Google Chat)

```sh
# example request. (See examples/send_alert.sh)
➜ curl -XPOST -d"$alerts1" http://localhost:6000/create -i
➜ curl -XPOST -d"$alerts1" http://localhost:6000/create?room_name=<room> -i
{"status":"success","message":"Alert sent","data":null}
```

- GET `/ping` (Health check endpoint)
_`room_name`_ param is required. The same `room_name` should be present in `app.chat` section. You can refer to the `config.sample` for examples.

- GET `/ping` (Health check endpoint)

```sh
➜ curl http://localhost:6000/ping
{"status":"success","data":{"buildVersion":"025a3a3 (2018-12-26 22:04:46 +0530)","buildDate":"2018-12-27 10:41:52","ping":"pong"}}
Expand All @@ -83,4 +83,4 @@ PRs on Feature Requests, Bug fixes are welcome. Feel free to open an issue and h

## License

[MIT](license)
[MIT](license)
33 changes: 17 additions & 16 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
- [x] Add route for inserting new alert
- [x] Add route for inserting new alert

- [x] Parse alertmanager reponse
- [x] Parse alertmanager reponse

- [x] Add example payload - curl script
- [x] Add example payload - curl script

- [x] Log only errors (unix philosophy)
- [x] Log only errors (unix philosophy)

- [x] Response check from google chat
- [x] Response check from google chat

- [x] Gomod
- [x] Gomod

- [x] Refactor to new name
- [x] Refactor to new name

- [ ] Add tests
- [ ] Add tests

- [x] Use middleware to pass around args/context/return err signature instead of naked returns (ugly! *must fix)
- [x] Use middleware to pass around args/context/return err signature instead of naked returns (ugly! \*must fix)

- [ ] Create a package
- [ ] Create a package

- [x] Goreleaser binary
- [x] Goreleaser binary

- [x] Images (Google chat instructions + bot usage (gif) + Logo)
- [x] Images (Google chat instructions + bot usage (gif) + Logo)

- [x] Add documentation + comments everywhere
- [x] Add documentation + comments everywhere

- [x] Add License, CoC, CONTRIBUTION.md
- [x] Add License, CoC, CONTRIBUTION.md

- [ ] Add metrics handler (feature request)
- [ ] Add metrics handler (feature request)

- [ ] Add verbose mode (optional, low priority)
- [ ] Add verbose mode (optional, low priority)

- [x] Add support for chat room param, so people can use multiple chat rooms based on alertmanager config (DUH! How could I possibly miss this)
15 changes: 14 additions & 1 deletion api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"

alerttemplate "github.com/prometheus/alertmanager/template"
"github.com/spf13/viper"
)

const (
Expand Down Expand Up @@ -78,13 +79,25 @@ func handleNewAlert(a *App, w http.ResponseWriter, r *http.Request) (code int, m
alertData = alerttemplate.Data{}
n = a.notifier
)
// fetch the room_name param. This room_name is used to map the webhook URL from the config.
// just an abstraction, for a more humanised version and to not end up making alertmanager config
// a mess by not flooding with google chat webhook URLs all over the place.
roomName := r.URL.Query().Get("room_name")
if roomName == "" {
return http.StatusBadRequest, "Missing required room_name param", nil, excepBadRequest, err
}
webHookURL := viper.GetString(fmt.Sprintf("app.chat.%s.notification_url", roomName))
if webHookURL == "" {
errMsg := fmt.Sprintf("Webhook URL not configured for room_name: %s", roomName)
return http.StatusBadRequest, errMsg, nil, excepBadRequest, err
}
// decode request payload from Alertmanager in a struct
if err := json.NewDecoder(r.Body).Decode(&alertData); err != nil {
errMsg := fmt.Sprintf("Error while decoding alertmanager response: %s", err)
return http.StatusBadRequest, errMsg, nil, excepBadRequest, err
}
// send notification to chat
err = sendMessageToChat(alertData.Alerts, &n)
err = sendMessageToChat(alertData.Alerts, &n, webHookURL)
if err != nil {
return http.StatusInternalServerError, "Something went wrong while sending alert notification", nil, excepGeneral, err
}
Expand Down
7 changes: 6 additions & 1 deletion config.toml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ keepalive_timeout=300000
max_body_size=20000

[app]
notification_url = "https://chat.googleapis.com/v1/spaces/xxx/messages?key=abc-xyz&token=token-unique-key%3D"
template_file = "message.tmpl"

[app.http_client]
max_idle_conns = 100
request_timeout = 8000

[app.chat.alertManagerTestRoom]
notification_url = "https://chat.googleapis.com/v1/spaces/xxx/messages?key=abc-xyz&token=token-unique-key%3D"

[app.chat.awesomeRoomTwo]
notification_url = "https://chat.googleapis.com/v1/spaces/xxx/messages?key=abc-xyz&token=token-unique-key%3D"
2 changes: 1 addition & 1 deletion examples/send_alert.sh
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,4 @@ alerts1='{
"groupKey":"{}:{alertname=\"Instance Down\"}"
}'

curl -XPOST -d"$alerts1" http://localhost:6000/create -i
curl -XPOST -d"$alerts1" http://localhost:6000/create?room_name=alertManagerTestRoom -i
4 changes: 2 additions & 2 deletions helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
alerttemplate "github.com/prometheus/alertmanager/template"
)

func sendMessageToChat(alerts []alerttemplate.Alert, notif *Notifier) error {
func sendMessageToChat(alerts []alerttemplate.Alert, notif *Notifier, webHookURL string) error {
var (
message = ChatNotification{}
str strings.Builder
Expand Down Expand Up @@ -40,5 +40,5 @@ func sendMessageToChat(alerts []alerttemplate.Alert, notif *Notifier) error {
// prepare request payload for Google chat webhook endpoint
message.Text = str.String()

return notif.PushNotification(message)
return notif.PushNotification(message, webHookURL)
}
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func initPackage() {
}}

// Notifier for sending alerts.
notifier := NewNotifier(viper.GetString("app.notification_url"), *httpClient)
notifier := NewNotifier(*httpClient)

context := &App{notifier, sysLog}

Expand Down
7 changes: 3 additions & 4 deletions notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,20 @@ type Notifier struct {
}

// NewNotifier initialises a new instance of the Notifier.
func NewNotifier(root string, h http.Client) Notifier {
func NewNotifier(h http.Client) Notifier {
return Notifier{
root: root,
httpClient: &h,
}
}

// PushNotification pushes out a notification to Google Chat Room.
func (n *Notifier) PushNotification(notif ChatNotification) error {
func (n *Notifier) PushNotification(notif ChatNotification, webHookURL string) error {
out, err := json.Marshal(notif)
if err != nil {
return err
}
// prepare POST request to webhook endpoint
req, err := http.NewRequest("POST", n.root, bytes.NewBuffer(out))
req, err := http.NewRequest("POST", webHookURL, bytes.NewBuffer(out))
if err != nil {
return err
}
Expand Down

0 comments on commit 9121985

Please sign in to comment.