Skip to content

Commit b42f497

Browse files
committed
Add starting abstractions and impl for sending alerts via Gmail and Grafana
1 parent 38447aa commit b42f497

26 files changed

+2074
-0
lines changed

alerts/alerts.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package alerts
2+
3+
import (
4+
"fmt"
5+
"log"
6+
)
7+
8+
type Alerter interface {
9+
SendAlert(title, content string) error
10+
}
11+
12+
type AlertsConfig struct {
13+
FailOnError bool
14+
Gmail MailerConfig
15+
Grafana GrafanaConfig
16+
}
17+
18+
type AlertsManager struct {
19+
failOnError bool
20+
alerters []Alerter
21+
}
22+
23+
func NewAlertsManager(config AlertsConfig) (*AlertsManager, error) {
24+
alerters := []Alerter{}
25+
26+
if config.Gmail.Enabled {
27+
log.Println("Initializig Gmail alerter ...")
28+
gmailAlerter, err := NewGmailAlerter(config.Gmail)
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
alerters = append(alerters, gmailAlerter)
34+
log.Println("Gmail alerter ready")
35+
}
36+
37+
if config.Grafana.Enabled {
38+
log.Println("Initializig Grafana alerter ...")
39+
grafanaAlerter := NewGrafanaAlerter(config.Grafana)
40+
alerters = append(alerters, grafanaAlerter)
41+
log.Println("Grafana alerter ready")
42+
}
43+
44+
return &AlertsManager{
45+
failOnError: config.FailOnError,
46+
alerters: alerters,
47+
}, nil
48+
}
49+
50+
func (am *AlertsManager) DispatchAlert(repo string, cause error) error {
51+
title := fmt.Sprintf("Minima - failure for %s", repo)
52+
content := fmt.Sprintf("Cause: %v", cause)
53+
54+
for _, a := range am.alerters {
55+
if err := a.SendAlert(title, content); err != nil {
56+
log.Printf("Alerter error: %v\n", err)
57+
if am.failOnError {
58+
return err
59+
}
60+
}
61+
}
62+
return nil
63+
}

alerts/gmail.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package alerts
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"gopkg.in/gomail.v2"
8+
)
9+
10+
const gmailSMTP = "smtp.gmail.com"
11+
const gmailSMTPPort = 587
12+
13+
type MailerConfig struct {
14+
Enabled bool
15+
Account string
16+
Password string
17+
From string
18+
Recipients []string
19+
}
20+
21+
type GmailAlerter struct {
22+
mailClient gomail.SendCloser
23+
from string
24+
recipients []string
25+
}
26+
27+
func NewGmailAlerter(config MailerConfig) (*GmailAlerter, error) {
28+
d := gomail.NewDialer(gmailSMTP, gmailSMTPPort, config.Account, config.Password)
29+
sender, err := d.Dial()
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
return &GmailAlerter{
35+
mailClient: sender,
36+
from: config.From,
37+
recipients: config.Recipients,
38+
}, nil
39+
}
40+
41+
// Alerter interface implementation
42+
func (config *GmailAlerter) SendAlert(title, content string) error {
43+
log.Printf("Sending alert via gmail to: %v\n", config.recipients)
44+
45+
if err := config.sendEmail(title, content); err != nil {
46+
return fmt.Errorf("failed to send alert via email: %v", err)
47+
}
48+
return nil
49+
}
50+
51+
func (config *GmailAlerter) sendEmail(subject, body string) error {
52+
m := gomail.NewMessage()
53+
m.SetHeader("From", config.from)
54+
m.SetHeader("To", config.recipients...)
55+
m.SetHeader("Subject", subject)
56+
m.SetBody("text/plain", body)
57+
58+
return gomail.Send(config.mailClient, m)
59+
}

alerts/grafana.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package alerts
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"time"
9+
)
10+
11+
type GrafanaConfig struct {
12+
Enabled bool
13+
AlertTitle string
14+
APIUrl string
15+
APIKey string
16+
}
17+
18+
type GrafanaAlerter struct {
19+
httpClient *http.Client
20+
key string
21+
alertsUrl string
22+
}
23+
24+
type AlertPayload struct {
25+
Title string `json:"title"`
26+
Message string `json:"message"`
27+
AlertStatus string `json:"status"`
28+
}
29+
30+
func NewGrafanaAlerter(config GrafanaConfig) *GrafanaAlerter {
31+
client := &http.Client{Timeout: 30 * time.Second}
32+
33+
return &GrafanaAlerter{
34+
httpClient: client,
35+
alertsUrl: config.APIUrl + "alerts",
36+
key: config.APIKey,
37+
}
38+
}
39+
40+
// Alerter interface implementation
41+
func (g *GrafanaAlerter) SendAlert(title, content string) error {
42+
fmt.Println("Sending Grafana alert")
43+
44+
if err := g.postGrafanaAlert(title, content); err != nil {
45+
return fmt.Errorf("failed to send Grafana alert: %v", err)
46+
}
47+
return nil
48+
}
49+
50+
func (g *GrafanaAlerter) postGrafanaAlert(title, msg string) error {
51+
alert := AlertPayload{
52+
Title: title,
53+
Message: msg,
54+
AlertStatus: "alerting",
55+
}
56+
57+
jsonData, err := json.Marshal(alert)
58+
if err != nil {
59+
return err
60+
}
61+
62+
req, err := http.NewRequest("POST", g.alertsUrl, bytes.NewBuffer(jsonData))
63+
if err != nil {
64+
return err
65+
}
66+
req.Header.Set("Content-Type", "application/json")
67+
req.Header.Set("Authorization", "Bearer "+g.key)
68+
69+
resp, err := g.httpClient.Do(req)
70+
if err != nil {
71+
return err
72+
}
73+
defer resp.Body.Close()
74+
75+
if resp.StatusCode != http.StatusOK {
76+
return fmt.Errorf("failed to send Grafana alert, status code: %d", resp.StatusCode)
77+
}
78+
return nil
79+
}

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ require (
88
github.com/spf13/cobra v1.9.1
99
github.com/stretchr/testify v1.10.0
1010
golang.org/x/crypto v0.36.0
11+
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
1112
gopkg.in/yaml.v2 v2.4.0
1213
)
1314

15+
16+
1417
require (
1518
github.com/davecgh/go-spew v1.1.1 // indirect
1619
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1720
github.com/jmespath/go-jmespath v0.4.0 // indirect
1821
github.com/pmezard/go-difflib v1.0.0 // indirect
1922
github.com/spf13/pflag v1.0.6 // indirect
23+
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
2024
gopkg.in/yaml.v3 v3.0.1 // indirect
2125
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
2424
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
2525
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
2626
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
27+
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
28+
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
2729
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2830
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
31+
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
32+
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
2933
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
3034
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
3135
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)