1
2
3
4
5
6
7
8
9
10
11
12
13
14 package notify
15
16 import (
17 "context"
18 "crypto/sha256"
19 "fmt"
20 "io"
21 "net/http"
22 "net/url"
23 "strings"
24
25 "github.com/go-kit/log"
26 "github.com/go-kit/log/level"
27 "github.com/pkg/errors"
28 "github.com/prometheus/common/version"
29
30 "github.com/prometheus/alertmanager/template"
31 "github.com/prometheus/alertmanager/types"
32 )
33
34
35 const truncationMarker = "…"
36
37
38 var UserAgentHeader = fmt.Sprintf("Alertmanager/%s", version.Version)
39
40
41 func RedactURL(err error) error {
42 e, ok := err.(*url.Error)
43 if !ok {
44 return err
45 }
46 e.URL = "<redacted>"
47 return e
48 }
49
50
51 func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
52 return request(ctx, client, http.MethodGet, url, "", nil)
53 }
54
55
56 func PostJSON(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error) {
57 return post(ctx, client, url, "application/json", body)
58 }
59
60
61 func PostText(ctx context.Context, client *http.Client, url string, body io.Reader) (*http.Response, error) {
62 return post(ctx, client, url, "text/plain", body)
63 }
64
65 func post(ctx context.Context, client *http.Client, url, bodyType string, body io.Reader) (*http.Response, error) {
66 return request(ctx, client, http.MethodPost, url, bodyType, body)
67 }
68
69 func request(ctx context.Context, client *http.Client, method, url, bodyType string, body io.Reader) (*http.Response, error) {
70 req, err := http.NewRequest(method, url, body)
71 if err != nil {
72 return nil, err
73 }
74 req.Header.Set("User-Agent", UserAgentHeader)
75 if bodyType != "" {
76 req.Header.Set("Content-Type", bodyType)
77 }
78 return client.Do(req.WithContext(ctx))
79 }
80
81
82
83 func Drain(r *http.Response) {
84 io.Copy(io.Discard, r.Body)
85 r.Body.Close()
86 }
87
88
89 func TruncateInRunes(s string, n int) (string, bool) {
90 r := []rune(s)
91 if len(r) <= n {
92 return s, false
93 }
94
95 if n <= 3 {
96 return string(r[:n]), true
97 }
98
99 return string(r[:n-1]) + truncationMarker, true
100 }
101
102
103 func TruncateInBytes(s string, n int) (string, bool) {
104
105 if len(s) <= n {
106 return s, false
107 }
108
109
110 if n <= 3 {
111 switch n {
112 case 3:
113 return truncationMarker, true
114 default:
115 return strings.Repeat(".", n), true
116 }
117 }
118
119
120 r := []rune(s)
121 truncationTarget := n - 3
122
123
124 truncatedRunes := r[:truncationTarget]
125 for len(string(truncatedRunes)) > truncationTarget {
126 truncatedRunes = r[:len(truncatedRunes)-1]
127 }
128
129 return string(truncatedRunes) + truncationMarker, true
130 }
131
132
133
134 func TmplText(tmpl *template.Template, data *template.Data, err *error) func(string) string {
135 return func(name string) (s string) {
136 if *err != nil {
137 return
138 }
139 s, *err = tmpl.ExecuteTextString(name, data)
140 return s
141 }
142 }
143
144
145
146 func TmplHTML(tmpl *template.Template, data *template.Data, err *error) func(string) string {
147 return func(name string) (s string) {
148 if *err != nil {
149 return
150 }
151 s, *err = tmpl.ExecuteHTMLString(name, data)
152 return s
153 }
154 }
155
156
157 type Key string
158
159
160 func ExtractGroupKey(ctx context.Context) (Key, error) {
161 key, ok := GroupKey(ctx)
162 if !ok {
163 return "", errors.Errorf("group key missing")
164 }
165 return Key(key), nil
166 }
167
168
169
170 func (k Key) Hash() string {
171 h := sha256.New()
172
173
174 h.Write([]byte(string(k)))
175 return fmt.Sprintf("%x", h.Sum(nil))
176 }
177
178 func (k Key) String() string {
179 return string(k)
180 }
181
182
183 func GetTemplateData(ctx context.Context, tmpl *template.Template, alerts []*types.Alert, l log.Logger) *template.Data {
184 recv, ok := ReceiverName(ctx)
185 if !ok {
186 level.Error(l).Log("msg", "Missing receiver")
187 }
188 groupLabels, ok := GroupLabels(ctx)
189 if !ok {
190 level.Error(l).Log("msg", "Missing group labels")
191 }
192 return tmpl.Data(recv, groupLabels, alerts...)
193 }
194
195 func readAll(r io.Reader) string {
196 if r == nil {
197 return ""
198 }
199 bs, err := io.ReadAll(r)
200 if err != nil {
201 return ""
202 }
203 return string(bs)
204 }
205
206
207
208
209 type Retrier struct {
210
211 CustomDetailsFunc func(code int, body io.Reader) string
212
213 RetryCodes []int
214 }
215
216
217
218
219 func (r *Retrier) Check(statusCode int, body io.Reader) (bool, error) {
220
221 if statusCode/100 == 2 {
222 return false, nil
223 }
224
225
226 retry := statusCode/100 == 5
227 if !retry {
228 for _, code := range r.RetryCodes {
229 if code == statusCode {
230 retry = true
231 break
232 }
233 }
234 }
235
236 s := fmt.Sprintf("unexpected status code %v", statusCode)
237 var details string
238 if r.CustomDetailsFunc != nil {
239 details = r.CustomDetailsFunc(statusCode, body)
240 } else {
241 details = readAll(body)
242 }
243 if details != "" {
244 s = fmt.Sprintf("%s: %s", s, details)
245 }
246 return retry, errors.New(s)
247 }
248
View as plain text