forked from bosun-monitor/bosun
-
Notifications
You must be signed in to change notification settings - Fork 0
/
conf.go
424 lines (376 loc) · 12.1 KB
/
conf.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
package conf // import "bosun.org/cmd/bosun/conf"
import (
"bytes"
"fmt"
"hash/fnv"
"net/mail"
"net/url"
"os/exec"
"regexp"
"strings"
"time"
"github.com/influxdata/influxdb/client"
"bosun.org/cmd/bosun/expr"
"bosun.org/cmd/bosun/expr/parse"
"bosun.org/graphite"
"bosun.org/models"
"bosun.org/opentsdb"
htemplate "html/template"
ttemplate "text/template"
"bosun.org/slog"
)
// SystemConfProvider providers all the information about the system configuration.
// the interface exists to ensure that no changes are made to the system configuration
// outside of the package without a setter
type SystemConfProvider interface {
GetHTTPListen() string
GetRelayListen() string
GetSMTPHost() string
GetSMTPUsername() string // SMTP username
GetSMTPPassword() string // SMTP password
GetPing() bool
GetPingDuration() time.Duration
GetEmailFrom() string
GetLedisDir() string
GetLedisBindAddr() string
GetRedisHost() string
GetRedisDb() int
GetRedisPassword() string
GetTimeAndDate() []int
GetSearchSince() time.Duration
GetCheckFrequency() time.Duration
GetDefaultRunEvery() int
GetUnknownThreshold() int
GetMinGroupSize() int
GetShortURLKey() string
GetInternetProxy() string
GetRuleFilePath() string
SaveEnabled() bool
ReloadEnabled() bool
GetCommandHookPath() string
SetTSDBHost(tsdbHost string)
GetTSDBHost() string
GetLogstashElasticHosts() expr.LogstashElasticHosts
GetAnnotateElasticHosts() expr.ElasticHosts
GetAnnotateIndex() string
// Contexts
GetTSDBContext() opentsdb.Context
GetGraphiteContext() graphite.Context
GetInfluxContext() client.Config
GetLogstashContext() expr.LogstashElasticHosts
GetElasticContext() expr.ElasticHosts
AnnotateEnabled() bool
MakeLink(string, *url.Values) string
EnabledBackends() EnabledBackends
}
// ValidateSystemConf runs sanity checks on the system configuration
func ValidateSystemConf(sc SystemConfProvider) error {
hasSMTPHost := sc.GetSMTPHost() != ""
hasEmailFrom := sc.GetEmailFrom() != ""
if hasSMTPHost != hasEmailFrom {
return fmt.Errorf("email notififications require that both SMTP Host and EmailFrom be set")
}
if sc.GetDefaultRunEvery() <= 0 {
return fmt.Errorf("default run every must be greater than 0, is %v", sc.GetDefaultRunEvery())
}
return nil
}
// RuleConfProvider is an interface for accessing information that bosun needs to know about
// rule configuration. Rule configuration includes Macros, Alerts, Notifications, Lookup
// tables, squelching, and variable expansion. Currently there is only one implementation of
// this inside bosun in the rule package. The interface exists to ensure that the rest of
// Bosun does not manipulate the rule configuration in unexpected ways. Also so the possibility
// of an alternative store for rules can exist the future. However, when this is added it is expected
// that the interface will change significantly.
type RuleConfProvider interface {
RuleConfWriter
GetUnknownTemplate() *Template
GetTemplate(string) *Template
GetAlerts() map[string]*Alert
GetAlert(string) *Alert
GetNotifications() map[string]*Notification
GetNotification(string) *Notification
GetLookup(string) *Lookup
AlertSquelched(*Alert) func(opentsdb.TagSet) bool
Squelched(*Alert, opentsdb.TagSet) bool
Expand(string, map[string]string, bool) string
GetFuncs(EnabledBackends) map[string]parse.Func
}
// RuleConfWriter is a collection of the methods that are used to manipulate the configuration
// Save methods will trigger the reload that has been passed to the rule configuration
type RuleConfWriter interface {
BulkEdit(BulkEditRequest) error
GetRawText() string
GetHash() string
SaveRawText(rawConf, diff, user, message string, args ...string) error
RawDiff(rawConf string) (string, error)
SetReload(reload func() error)
SetSaveHook(SaveHook)
}
// Squelch is a map of tag keys to regexes that are applied to tag values. Squelches
// are used to filter results from query responses
type Squelch map[string]*regexp.Regexp
// Squelches is a collection of Squelch
type Squelches []Squelch
// Add adds a sqluech baed on the tags in the first argument. The value of the tag
// is a regular expression. Tags are passed as a string in the format of
func (s *Squelches) Add(v string) error {
tags, err := opentsdb.ParseTags(v)
if tags == nil && err != nil {
return err
}
sq := make(Squelch)
for k, v := range tags {
re, err := regexp.Compile(v)
if err != nil {
return err
}
sq[k] = re
}
*s = append(*s, sq)
return nil
}
// Squelched takes a tag set and returns true if the given
// tagset should be squelched based on the Squelches
func (s *Squelches) Squelched(tags opentsdb.TagSet) bool {
for _, squelch := range *s {
if squelch.Squelched(tags) {
return true
}
}
return false
}
// Squelched takes a tag set and returns true if the given
// tagset should be squelched based on the Squelche
func (s Squelch) Squelched(tags opentsdb.TagSet) bool {
if len(s) == 0 {
return false
}
for k, v := range s {
tagv, ok := tags[k]
if !ok || !v.MatchString(tagv) {
return false
}
}
return true
}
// Template stores information about a notification template. Templates
// are based on Go's text and html/template.
type Template struct {
Text string
Vars
Name string
Body *htemplate.Template `json:"-"`
Subject *ttemplate.Template `json:"-"`
RawBody, RawSubject string
Locator `json:"-"`
}
// Notification stores information about a notification. A notification
// is the definition of an action that should be performed when an
// alert is triggered
type Notification struct {
Text string
Vars
Name string
Email []*mail.Address
Post, Get *url.URL
Body *ttemplate.Template
Print bool
Next *Notification
Timeout time.Duration
ContentType string
RunOnActions bool
UseBody bool
NextName string `json:"-"`
RawEmail string `json:"-"`
RawPost, RawGet string `json:"-"`
RawBody string `json:"-"`
Locator `json:"-"`
}
// Vars holds a map of variable names to the variable's value
type Vars map[string]string
// Notifications contains a mapping of notification names to
// all notifications in the configuration. The Lookups Property
// enables notification lookups - the ability to trigger different
// notifications based an alerts resulting tags
type Notifications struct {
Notifications map[string]*Notification `json:"-"`
// Table key -> table
Lookups map[string]*Lookup
}
// Get returns the set of notifications based on given tags and applys any notification
// lookup tables
func (ns *Notifications) Get(c RuleConfProvider, tags opentsdb.TagSet) map[string]*Notification {
nots := make(map[string]*Notification)
for name, n := range ns.Notifications {
nots[name] = n
}
for key, lookup := range ns.Lookups {
l := lookup.ToExpr()
val, ok := l.Get(key, tags)
if !ok {
continue
}
ns := make(map[string]*Notification)
for _, s := range strings.Split(val, ",") {
s = strings.TrimSpace(s)
n := c.GetNotification(s)
if n == nil {
continue // TODO error here?
}
ns[s] = n
}
for name, n := range ns {
nots[name] = n
}
}
return nots
}
// GetNotificationChains returns the warn or crit notification chains for a configured
// alert. Each chain is a list of notification names. If a notification name
// as already been seen in the chain it ends the list with the notification
// name with a of "..." which indicates that the chain will loop.
func GetNotificationChains(c RuleConfProvider, n map[string]*Notification) [][]string {
chains := [][]string{}
for _, root := range n {
chain := []string{}
seen := make(map[string]bool)
var walkChain func(next *Notification)
walkChain = func(next *Notification) {
if next == nil {
chains = append(chains, chain)
return
}
if seen[next.Name] {
chain = append(chain, fmt.Sprintf("...%v", next.Name))
chains = append(chains, chain)
return
}
chain = append(chain, next.Name)
seen[next.Name] = true
walkChain(next.Next)
}
walkChain(root)
}
return chains
}
// A Lookup is used to return values based on the tags of a response. It
// provides switch/case functionality
type Lookup struct {
Text string
Name string
Tags []string
Entries []*Entry
Locator `json:"-"`
}
func (lookup *Lookup) ToExpr() *ExprLookup {
l := ExprLookup{
Tags: lookup.Tags,
}
for _, entry := range lookup.Entries {
l.Entries = append(l.Entries, entry.ExprEntry)
}
return &l
}
// Entry is an entry in a Lookup.
type Entry struct {
*ExprEntry
Def string
Name string
}
// Macro provides the ability to reuse partial sections of
// alert definition text. Macros can contain other macros
type Macro struct {
Text string
Pairs interface{} // this is BAD TODO
Name string
Locator `json:"-"`
}
// Alert stores all information about alerts. All other major
// sections of rule configuration are referenced by alerts including
// Templates, Macros, and Notifications. Alerts hold the expressions
// that determine the Severity of the Alert. There are also flags the
// alter the behavior of the alert and how the expression is evaluated
type Alert struct {
Text string
Vars
*Template `json:"-"`
Name string
Crit *expr.Expr `json:",omitempty"`
Warn *expr.Expr `json:",omitempty"`
Depends *expr.Expr `json:",omitempty"`
Squelch Squelches `json:"-"`
CritNotification *Notifications
WarnNotification *Notifications
Unknown time.Duration
MaxLogFrequency time.Duration
IgnoreUnknown bool
UnknownsNormal bool
UnjoinedOK bool `json:",omitempty"`
Log bool
RunEvery int
ReturnType models.FuncType
TemplateName string `json:"-"`
RawSquelch []string `json:"-"`
Locator `json:"-"`
}
// A Locator stores the information about the location of the rule in the underlying
// rule store
type Locator interface{}
// BulkEditRequest is a collection of BulkEditRequest to be applied sequentially
type BulkEditRequest []EditRequest
// EditRequest is a proposed edit to the config file for sections. The Name is the name of section,
// Type can be "alert", "template", "notification", "lookup", or "macro". The Text should be the full
// text of the definition, including the delaration and brackets (i.e. "alert foo { .. }"). If Delete
// is true then the section will be deleted. In order to rename something, specify the old name in the
// Name field but have the Text definition contain the new name.
type EditRequest struct {
Name string
Type string
Text string
Delete bool
}
// SaveHook is a function that is passed files as a string (currently the only implementation
// has a single file, so there is no convention for the format of multiple files yet), a user
// a message and vargs. A SaveHook is called when using bosun to save the config. A save is reverted
// when the SaveHook returns an error.
type SaveHook func(files, user, message string, args ...string) error
// MakeSaveCommandHook takes a fuction based on the command name and will run it on save passing files, user,
// message, args... as arguments to the command. For the SaveHook function that is returned, If the command fails
// to execute or returns a non normal output then an error is returned.
func MakeSaveCommandHook(cmdName string) (f SaveHook, err error) {
_, err = exec.LookPath(cmdName)
if err != nil {
return f, fmt.Errorf("command %v not found, failed to create save hook: %v", cmdName, err)
}
f = func(files, user, message string, args ...string) error {
cArgs := []string{files, user, message}
cArgs = append(cArgs, args...)
slog.Infof("executing save hook %v\n", cmdName)
c := exec.Command(cmdName, cArgs...)
var cOut bytes.Buffer
var cErr bytes.Buffer
c.Stdout = &cOut
c.Stderr = &cErr
err := c.Start()
if err != nil {
return err
}
err = c.Wait()
if err != nil {
slog.Warning(cErr.String())
return err
}
slog.Infof("save hook ouput: %v\n", cOut.String())
return nil
}
return
}
// GenHash generates a unique hash of a string. It is used so we can compare
// edited text configuration to running text configuration and see if it has
// changed
func GenHash(s string) string {
h := fnv.New32a()
h.Write([]byte(s))
return fmt.Sprintf("%v", h.Sum32())
}