-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnotifier.go
137 lines (109 loc) · 3.03 KB
/
notifier.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package bqloader
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"sync"
"github.com/rs/zerolog/log"
"golang.org/x/xerrors"
)
// Notifier notifies results for each event.
type Notifier interface {
Notify(context.Context, *Result) error
}
// Result is a result for each event.
type Result struct {
Event Event
Handler *Handler
Error error
}
// SlackNotifier is a notifier for Slack.
// SlackNotifier requires bot token and permissions.
// Recommended permissions are chat:write, chat:write.customize and chat:write.public.
type SlackNotifier struct {
Channel string
Token string
// Optional.
IconEmoji string
// Optional.
Username string
// Optional.
HTTPClient *http.Client
once sync.Once
}
type slackMessage struct {
Channel string `json:"channel"`
IconEmoji string `json:"icon_emoji,omitempty"`
Text string `json:"text"`
Username string `json:"username,omitempty"`
}
type slackResponse struct {
OK bool `json:"ok"`
Error string `json:"error"`
}
// Notify notifies results to Slack channel.
func (n *SlackNotifier) Notify(ctx context.Context, r *Result) error {
l := log.Ctx(ctx)
n.once.Do(func() {
if n.HTTPClient == nil {
n.HTTPClient = &http.Client{}
}
})
var text string
if r.Error == nil {
text = fmt.Sprintf(`:white_check_mark: %s handler successfully loaded %s`, r.Handler.Name, r.Event.Name)
} else {
text = fmt.Sprintf(`:x: %s handler failed to load %s: %s`, r.Handler.Name, r.Event.Name, r.Error)
}
m := &slackMessage{
Channel: n.Channel,
IconEmoji: n.IconEmoji,
Text: text,
Username: n.Username,
}
l.Debug().Msgf("m = %+v", m)
if err := n.postMessage(ctx, m); err != nil {
return xerrors.Errorf("slack postMessage failed: %w", err)
}
return nil
}
func (n *SlackNotifier) postMessage(ctx context.Context, m *slackMessage) error {
l := log.Ctx(ctx)
reqJSON, err := json.Marshal(m)
if err != nil {
return xerrors.Errorf("failed to marshal json: %w", err)
}
req, err := http.NewRequest("POST", "https://slack.com/api/chat.postMessage", bytes.NewReader(reqJSON))
if err != nil {
return xerrors.Errorf("failed to build http request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
l.Debug().Msgf("req = %+v", req)
req.Header.Set("Authorization", "Bearer "+n.Token)
resp, err := n.HTTPClient.Do(req.WithContext(ctx))
if err != nil {
return xerrors.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
l.Debug().Msgf("resp = %+v", resp)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return xerrors.Errorf("failed to read response body: %w", err)
}
l.Debug().Msgf("body = %s", body)
if resp.StatusCode >= http.StatusBadRequest {
return xerrors.Errorf(
"slack webhook request failed with status code %d (%s)", resp.StatusCode, body)
}
var sres slackResponse
if err := json.Unmarshal(body, &sres); err != nil {
return xerrors.Errorf("failed to unmarshal response body: %w", err)
}
if !sres.OK {
return xerrors.Errorf("failed to send message: %s", sres.Error)
}
return nil
}