Skip to content

Commit 64c8f92

Browse files
committed
Add starting abstractions and impl for sending alerts via Gmail and Grafana
1 parent 967c7fd commit 64c8f92

File tree

3 files changed

+201
-0
lines changed

3 files changed

+201
-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+
}

0 commit comments

Comments
 (0)