Skip to content

Commit a93a8a4

Browse files
committed
refactor: split into smaller files
1 parent 7069691 commit a93a8a4

File tree

10 files changed

+295
-219
lines changed

10 files changed

+295
-219
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
alertmanager-discord
22
alertmanager-discord.darwin
33
alertmanager-discord.linux
4+
/.idea/

README.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
This is a fork of https://github.com/benjojo/alertmanager-discord to enable a few more features.
2+
13
alertmanager-discord
24
===
35

46
Give this a webhook (with the DISCORD_WEBHOOK environment variable) and point it as a webhook on alertmanager, and it will post your alerts into a discord channel for you as they trigger:
57

6-
![](/.github/demo-new.png)
7-
88
## Warning
99

1010
This program is not a replacement to alertmanager, it accepts webhooks from alertmanager, not prometheus.
@@ -19,11 +19,6 @@ alerting: receivers:
1919
- static_configs: webhook_configs: - DISCORD_WEBHOOK=https://discordapp.com/api/we...
2020
- targets: - url: 'http://localhost:9094'
2121
- 127.0.0.1:9093
22-
23-
24-
25-
26-
2722
```
2823

2924
## Example alertmanager config:
@@ -56,4 +51,8 @@ receivers:
5651

5752
## Docker
5853

59-
If you run a fancy docker/k8s infra, you can find the docker hub repo here: https://hub.docker.com/r/benjojo/alertmanager-discord/
54+
See https://github.com/orgs/codingpot/packages/container/package/alertmanager-discord for the latest image.
55+
56+
```dockerfile
57+
FROM ghcr.io/codingpot/alertmanager-discord:v0
58+
```

alertman/alertman.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package alertman
2+
3+
import "encoding/json"
4+
5+
type AlertManAlert struct {
6+
Annotations struct {
7+
Description string `json:"description"`
8+
Summary string `json:"summary"`
9+
} `json:"annotations"`
10+
EndsAt string `json:"endsAt"`
11+
GeneratorURL string `json:"generatorURL"`
12+
Labels map[string]string `json:"labels"`
13+
StartsAt string `json:"startsAt"`
14+
Status string `json:"status"`
15+
}
16+
17+
type AlertManOut struct {
18+
Alerts []AlertManAlert `json:"alerts"`
19+
CommonAnnotations struct {
20+
Summary string `json:"summary"`
21+
} `json:"commonAnnotations"`
22+
CommonLabels struct {
23+
Alertname string `json:"alertname"`
24+
} `json:"commonLabels"`
25+
ExternalURL string `json:"externalURL"`
26+
GroupKey string `json:"groupKey"`
27+
GroupLabels struct {
28+
Alertname string `json:"alertname"`
29+
} `json:"groupLabels"`
30+
Receiver string `json:"receiver"`
31+
Status string `json:"status"`
32+
Version string `json:"version"`
33+
}
34+
35+
type rawPromAlert struct {
36+
Annotations struct {
37+
Description string `json:"description"`
38+
Summary string `json:"summary"`
39+
} `json:"annotations"`
40+
EndsAt string `json:"endsAt"`
41+
GeneratorURL string `json:"generatorURL"`
42+
Labels map[string]string `json:"labels"`
43+
StartsAt string `json:"startsAt"`
44+
Status string `json:"status"`
45+
}
46+
47+
func IsRawPromAlert(b []byte) bool {
48+
alertTest := make([]rawPromAlert, 0)
49+
err := json.Unmarshal(b, &alertTest)
50+
if err == nil {
51+
if len(alertTest) != 0 {
52+
if alertTest[0].Status == "" {
53+
// Ok it's more than likely then
54+
return true
55+
}
56+
}
57+
}
58+
return false
59+
}

detect-misconfig.go

Lines changed: 0 additions & 29 deletions
This file was deleted.

discord/discord.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package discord
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"github.com/codingpot/alertmanager-discord/alertman"
8+
"github.com/sirupsen/logrus"
9+
"net/http"
10+
"strings"
11+
)
12+
13+
// Discord color values
14+
const (
15+
ColorRed = 0x992D22
16+
ColorGreen = 0x2ECC71
17+
ColorGrey = 0x95A5A6
18+
)
19+
20+
type DiscordOut struct {
21+
Content string `json:"content"`
22+
Embeds []DiscordEmbed `json:"embeds"`
23+
}
24+
25+
type DiscordEmbed struct {
26+
Title string `json:"title"`
27+
Description string `json:"description"`
28+
Color int `json:"color"`
29+
Fields []DiscordEmbedField `json:"fields"`
30+
}
31+
32+
type DiscordEmbedField struct {
33+
Name string `json:"name"`
34+
Value string `json:"value"`
35+
}
36+
37+
func SendWebhook(amo *alertman.AlertManOut, discordWebhookURL string) {
38+
groupedAlerts := make(map[string][]alertman.AlertManAlert)
39+
40+
for _, alert := range amo.Alerts {
41+
groupedAlerts[alert.Status] = append(groupedAlerts[alert.Status], alert)
42+
}
43+
44+
for status, alerts := range groupedAlerts {
45+
DO := DiscordOut{}
46+
47+
RichEmbed := DiscordEmbed{
48+
Title: fmt.Sprintf("[%s:%d] %s", strings.ToUpper(status), len(alerts), amo.CommonLabels.Alertname),
49+
Description: amo.CommonAnnotations.Summary,
50+
Color: ColorGrey,
51+
Fields: []DiscordEmbedField{},
52+
}
53+
54+
if status == "firing" {
55+
RichEmbed.Color = ColorRed
56+
} else if status == "resolved" {
57+
RichEmbed.Color = ColorGreen
58+
}
59+
60+
if amo.CommonAnnotations.Summary != "" {
61+
DO.Content = fmt.Sprintf(" === %s === \n", amo.CommonAnnotations.Summary)
62+
}
63+
64+
for _, alert := range alerts {
65+
realname := alert.Labels["instance"]
66+
if strings.Contains(realname, "localhost") && alert.Labels["exported_instance"] != "" {
67+
realname = alert.Labels["exported_instance"]
68+
}
69+
70+
RichEmbed.Fields = append(RichEmbed.Fields, DiscordEmbedField{
71+
Name: fmt.Sprintf("[%s]: %s on %s", strings.ToUpper(status), alert.Labels["alertname"], realname),
72+
Value: alert.Annotations.Description,
73+
})
74+
}
75+
76+
DO.Embeds = []DiscordEmbed{RichEmbed}
77+
78+
DOD, _ := json.Marshal(DO)
79+
http.Post(discordWebhookURL, "application/json", bytes.NewReader(DOD))
80+
}
81+
}
82+
83+
func SendRawPromAlertWarn(discordWebhookURL string) {
84+
badString := `This program is suppose to be fed by alertmanager.` + "\n" +
85+
`It is not a replacement for alertmanager, it is a ` + "\n" +
86+
`webhook target for it. Please read the README.md ` + "\n" +
87+
`for guidance on how to configure it for alertmanager` + "\n" +
88+
`or https://prometheus.io/docs/alerting/latest/configuration/#webhook_config`
89+
90+
logrus.Print(`/!\ -- You have misconfigured this software -- /!\`)
91+
logrus.Print(`--- -- -- ---`)
92+
logrus.Print(badString)
93+
94+
DO := DiscordOut{
95+
Content: "",
96+
Embeds: []DiscordEmbed{
97+
{
98+
Title: "You have misconfigured this software",
99+
Description: badString,
100+
Color: ColorGrey,
101+
Fields: []DiscordEmbedField{},
102+
},
103+
},
104+
}
105+
106+
DOD, _ := json.Marshal(DO)
107+
_, err := http.Post(discordWebhookURL, "application/json", bytes.NewReader(DOD))
108+
if err != nil {
109+
logrus.Printf("failed to sned the alert")
110+
return
111+
}
112+
}

go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
module github.com/codingpot/alertmanager-discord
22

33
go 1.16
4+
5+
require (
6+
github.com/go-chi/chi/v5 v5.0.3
7+
github.com/sirupsen/logrus v1.8.1
8+
github.com/stretchr/testify v1.2.2
9+
)

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
4+
github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
5+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
6+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7+
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
8+
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
9+
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
10+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
11+
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
12+
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

internal/internal.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package internal
2+
3+
import (
4+
"encoding/json"
5+
"github.com/codingpot/alertmanager-discord/alertman"
6+
"github.com/codingpot/alertmanager-discord/discord"
7+
"github.com/go-chi/chi/v5"
8+
"github.com/go-chi/chi/v5/middleware"
9+
"github.com/sirupsen/logrus"
10+
"io/ioutil"
11+
"log"
12+
"net/http"
13+
"net/url"
14+
"regexp"
15+
)
16+
17+
func ValidateWebhookURL(webhookURL string) {
18+
if webhookURL == "" {
19+
log.Fatalf("Environment variable 'DISCORD_WEBHOOK' or CLI parameter 'webhook.url' not found.")
20+
}
21+
_, err := url.Parse(webhookURL)
22+
if err != nil {
23+
log.Fatalf("The Discord WebHook URL doesn't seem to be a valid URL.")
24+
}
25+
26+
re := regexp.MustCompile(`https://discord(?:app)?.com/api/webhooks/[0-9]{18}/[a-zA-Z0-9_-]+`)
27+
if ok := re.Match([]byte(webhookURL)); !ok {
28+
log.Printf("The Discord WebHook URL doesn't seem to be valid.")
29+
}
30+
}
31+
32+
func NewRouter(discordWebhookURL string) *chi.Mux {
33+
r := chi.NewRouter()
34+
r.Use(middleware.Logger, middleware.Heartbeat("/healthcheck"))
35+
36+
r.Post("/", func(w http.ResponseWriter, r *http.Request) {
37+
b, err := ioutil.ReadAll(r.Body)
38+
if err != nil {
39+
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
40+
return
41+
}
42+
43+
var amo alertman.AlertManOut
44+
err = json.Unmarshal(b, &amo)
45+
if err != nil {
46+
if alertman.IsRawPromAlert(b) {
47+
discord.SendRawPromAlertWarn(discordWebhookURL)
48+
return
49+
}
50+
51+
if len(b) > 1024 {
52+
logrus.Printf("Failed to unpack inbound alert request - %s...", string(b[:1023]))
53+
54+
} else {
55+
logrus.Printf("Failed to unpack inbound alert request - %s", string(b))
56+
}
57+
58+
return
59+
}
60+
61+
discord.SendWebhook(&amo, discordWebhookURL)
62+
})
63+
64+
return r
65+
}

internal/internal_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package internal
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
)
9+
10+
func TestNewRouter_Healthcheck(t *testing.T) {
11+
r := NewRouter("https://0.0.0.0")
12+
13+
server := httptest.NewServer(r)
14+
defer server.Close()
15+
16+
get, err := http.Get(server.URL + "/healthcheck")
17+
assert.NoError(t, err)
18+
assert.Equal(t, http.StatusOK, get.StatusCode)
19+
}

0 commit comments

Comments
 (0)