bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/conf/rule/rule.go (about) 1 package rule 2 3 import ( 4 "encoding/json" 5 "fmt" 6 htemplate "html/template" 7 "io/ioutil" 8 "os" 9 "regexp" 10 "runtime" 11 "strconv" 12 "strings" 13 "time" 14 15 "bosun.org/models" 16 17 "bosun.org/cmd/bosun/conf" 18 "bosun.org/cmd/bosun/conf/rule/parse" 19 "bosun.org/cmd/bosun/conf/template" 20 "bosun.org/cmd/bosun/expr" 21 eparse "bosun.org/cmd/bosun/expr/parse" 22 "bosun.org/cmd/bosun/sched/slack" 23 "bosun.org/opentsdb" 24 ) 25 26 type Conf struct { 27 Vars conf.Vars 28 Name string // Config file name 29 30 Templates map[string]*conf.Template 31 Alerts map[string]*conf.Alert 32 Notifications map[string]*conf.Notification `json:"-"` 33 RawText string 34 Macros map[string]*conf.Macro 35 Lookups map[string]*conf.Lookup 36 Squelch conf.Squelches `json:"-"` 37 NoSleep bool 38 39 reload func() error 40 backends conf.EnabledBackends 41 42 sysVars map[string]string 43 44 tree *parse.Tree 45 node parse.Node 46 unknownTemplate string 47 bodies *template.Template 48 subjects *template.Template 49 customTemplates map[string]*template.Template 50 squelch []string 51 52 writeLock chan bool 53 54 deferredSections map[string][]deferredSection // SectionType:[]deferredSection 55 saveHook conf.SaveHook // func that gets called on save if not nil 56 Hash string 57 } 58 59 type deferredSection struct { 60 LoadFunc func(*parse.SectionNode) 61 SectionNode *parse.SectionNode 62 } 63 64 func (c *Conf) AlertSquelched(a *conf.Alert) func(opentsdb.TagSet) bool { 65 return func(tags opentsdb.TagSet) bool { 66 return c.Squelched(a, tags) 67 } 68 } 69 70 func (c *Conf) Squelched(a *conf.Alert, tags opentsdb.TagSet) bool { 71 return c.Squelch.Squelched(tags) || a.Squelch.Squelched(tags) 72 } 73 74 // at marks the state to be on node n, for error reporting. 75 func (c *Conf) at(node parse.Node) { 76 c.node = node 77 } 78 79 func (c *Conf) error(err error) { 80 c.errorf(err.Error()) 81 } 82 83 // errorf formats the error and terminates processing. 84 func (c *Conf) errorf(format string, args ...interface{}) { 85 if c.node == nil { 86 format = fmt.Sprintf("conf: %s: %s", c.Name, format) 87 } else { 88 location, context := c.tree.ErrorContext(c.node) 89 format = fmt.Sprintf("conf: %s: at <%s>: %s", location, context, format) 90 } 91 panic(fmt.Errorf(format, args...)) 92 } 93 94 // errRecover is the handler that turns panics into returns from the top 95 // level of Parse. 96 func errRecover(errp *error) { 97 e := recover() 98 if e != nil { 99 switch err := e.(type) { 100 case runtime.Error: 101 panic(e) 102 case error: 103 *errp = err 104 default: 105 panic(e) 106 } 107 } 108 } 109 110 // parseNotifications parses the comma-separated string v for notifications and 111 // returns them. 112 func (c *Conf) parseNotifications(v string) (map[string]*conf.Notification, error) { 113 ns := make(map[string]*conf.Notification) 114 for _, s := range strings.Split(v, ",") { 115 s = strings.TrimSpace(s) 116 n := c.Notifications[s] 117 if n == nil { 118 return nil, fmt.Errorf("unknown notification %s", s) 119 } 120 ns[s] = n 121 } 122 return ns, nil 123 } 124 125 func ParseFile(fname string, backends conf.EnabledBackends, sysVars map[string]string) (*Conf, error) { 126 f, err := ioutil.ReadFile(fname) 127 if err != nil { 128 return nil, err 129 } 130 return NewConf(fname, backends, sysVars, string(f)) 131 } 132 133 func (c *Conf) SaveConf(newConf *Conf) error { 134 return ioutil.WriteFile(c.Name, []byte(newConf.RawText), os.FileMode(int(0640))) 135 } 136 137 func NewConf(name string, backends conf.EnabledBackends, sysVars map[string]string, text string) (c *Conf, err error) { 138 defer errRecover(&err) 139 c = &Conf{ 140 Name: name, 141 Vars: make(map[string]string), 142 Templates: make(map[string]*conf.Template), 143 Alerts: make(map[string]*conf.Alert), 144 Notifications: make(map[string]*conf.Notification), 145 RawText: text, 146 bodies: template.New("body").Funcs(defaultFuncs), 147 subjects: template.New("subject").Funcs(defaultFuncs), 148 customTemplates: map[string]*template.Template{}, 149 Lookups: make(map[string]*conf.Lookup), 150 Macros: make(map[string]*conf.Macro), 151 writeLock: make(chan bool, 1), 152 deferredSections: make(map[string][]deferredSection), 153 backends: backends, 154 sysVars: sysVars, 155 } 156 c.tree, err = parse.Parse(name, text) 157 if err != nil { 158 c.error(err) 159 } 160 saw := make(map[string]bool) 161 for _, n := range c.tree.Root.Nodes { 162 c.at(n) 163 switch n := n.(type) { 164 case *parse.PairNode: 165 c.seen(n.Key.Text, saw) 166 c.loadGlobal(n) 167 case *parse.SectionNode: 168 c.loadSection(n) 169 default: 170 c.errorf("unexpected parse node %s", n) 171 } 172 } 173 174 loadSections := func(sectionType string) { 175 for _, dSec := range c.deferredSections[sectionType] { 176 c.at(dSec.SectionNode) 177 dSec.LoadFunc(dSec.SectionNode) 178 } 179 } 180 181 loadSections("template") 182 loadSections("macro") 183 loadSections("notification") 184 loadSections("lookup") 185 loadSections("alert") 186 187 c.genHash() 188 return 189 } 190 191 func (c *Conf) loadGlobal(p *parse.PairNode) { 192 v := c.Expand(p.Val.Text, nil, false) 193 switch k := p.Key.Text; k { 194 case "unknownTemplate": 195 c.unknownTemplate = v 196 case "squelch": 197 c.squelch = append(c.squelch, v) 198 if err := c.Squelch.Add(v); err != nil { 199 c.error(err) 200 } 201 default: 202 if !strings.HasPrefix(k, "$") { 203 c.errorf("unknown key %s", k) 204 } 205 c.Vars[k] = v 206 c.Vars[k[1:]] = c.Vars[k] 207 } 208 } 209 210 func (c *Conf) loadSection(s *parse.SectionNode) { 211 ds := deferredSection{} 212 switch s.SectionType.Text { 213 case "template": 214 ds.LoadFunc = c.loadTemplate 215 case "alert": 216 ds.LoadFunc = c.loadAlert 217 case "notification": 218 ds.LoadFunc = c.loadNotification 219 case "macro": 220 ds.LoadFunc = c.loadMacro 221 case "lookup": 222 ds.LoadFunc = c.loadLookup 223 default: 224 c.errorf("unknown section type: %s", s.SectionType.Text) 225 } 226 ds.SectionNode = s 227 c.deferredSections[s.SectionType.Text] = append(c.deferredSections[s.SectionType.Text], ds) 228 } 229 230 type nodePair struct { 231 node parse.Node 232 key string 233 val string 234 } 235 236 type sectionType int 237 238 const ( 239 sNormal sectionType = iota 240 sMacro 241 ) 242 243 func (c *Conf) getPairs(s *parse.SectionNode, vars conf.Vars, st sectionType) (pairs []nodePair) { 244 saw := make(map[string]bool) 245 ignoreBadExpand := st == sMacro 246 add := func(n parse.Node, k, v string) { 247 c.seen(k, saw) 248 if vars != nil && strings.HasPrefix(k, "$") { 249 vars[k] = v 250 if st != sMacro { 251 vars[k[1:]] = v 252 } 253 } else { 254 pairs = append(pairs, nodePair{ 255 node: n, 256 key: k, 257 val: v, 258 }) 259 } 260 } 261 for _, n := range s.Nodes.Nodes { 262 c.at(n) 263 switch n := n.(type) { 264 case *parse.PairNode: 265 v := c.Expand(n.Val.Text, vars, ignoreBadExpand) 266 switch k := n.Key.Text; k { 267 case "macro": 268 m, ok := c.Macros[v] 269 if !ok { 270 c.errorf("macro not found: %s", v) 271 } 272 for _, p := range m.Pairs.([]nodePair) { 273 add(p.node, p.key, c.Expand(p.val, vars, ignoreBadExpand)) 274 } 275 default: 276 add(n, k, v) 277 } 278 default: 279 c.errorf("unexpected node") 280 } 281 } 282 return 283 } 284 285 func (c *Conf) loadLookup(s *parse.SectionNode) { 286 name := s.Name.Text 287 if _, ok := c.Lookups[name]; ok { 288 c.errorf("duplicate lookup name: %s", name) 289 } 290 l := conf.Lookup{ 291 Name: name, 292 } 293 l.Text = s.RawText 294 l.Locator = newSectionLocator(s) 295 var lookupTags opentsdb.TagSet 296 saw := make(map[string]bool) 297 for _, n := range s.Nodes.Nodes { 298 c.at(n) 299 switch n := n.(type) { 300 case *parse.SectionNode: 301 if n.SectionType.Text != "entry" { 302 c.errorf("unexpected subsection type") 303 } 304 tags, err := opentsdb.ParseTags(n.Name.Text) 305 if tags == nil && err != nil { 306 c.error(err) 307 } 308 if _, ok := saw[tags.String()]; ok { 309 c.errorf("duplicate entry") 310 } 311 saw[tags.String()] = true 312 if len(tags) == 0 { 313 c.errorf("lookup entries require tags") 314 } 315 empty := make(opentsdb.TagSet) 316 for k := range tags { 317 empty[k] = "" 318 } 319 if len(lookupTags) == 0 { 320 lookupTags = empty 321 for k := range empty { 322 l.Tags = append(l.Tags, k) 323 } 324 } else if !lookupTags.Equal(empty) { 325 c.errorf("lookup tags mismatch, expected %v", lookupTags) 326 } 327 e := conf.Entry{ 328 Def: n.RawText, 329 Name: n.Name.Text, 330 ExprEntry: &conf.ExprEntry{ 331 AlertKey: models.NewAlertKey("", tags), 332 Values: make(map[string]string), 333 }, 334 } 335 for _, en := range n.Nodes.Nodes { 336 c.at(en) 337 switch en := en.(type) { 338 case *parse.PairNode: 339 e.Values[en.Key.Text] = en.Val.Text 340 default: 341 c.errorf("unexpected node") 342 } 343 } 344 l.Entries = append(l.Entries, &e) 345 default: 346 c.errorf("unexpected node") 347 } 348 } 349 c.at(s) 350 c.Lookups[name] = &l 351 } 352 353 func (c *Conf) loadMacro(s *parse.SectionNode) { 354 name := s.Name.Text 355 if _, ok := c.Macros[name]; ok { 356 c.errorf("duplicate macro name: %s", name) 357 } 358 m := conf.Macro{ 359 Name: name, 360 } 361 m.Text = s.RawText 362 m.Locator = newSectionLocator(s) 363 pairs := c.getPairs(s, nil, sMacro) 364 for _, p := range pairs { 365 if _, ok := m.Pairs.([]nodePair); !ok { //bad 366 m.Pairs = []nodePair{} 367 } 368 m.Pairs = append(m.Pairs.([]nodePair), p) // bad 369 } 370 c.at(s) 371 c.Macros[name] = &m 372 } 373 374 // Note: Funcs that can error should return a pointer. In the error case the pointer 375 // should be non-nil. The exception to this is when a string is returned, in which case 376 // the string format of the error should be returned. This allows for error handling within 377 // templates for information that is helpful but not stricly necessary 378 var defaultFuncs = template.FuncMap{ 379 "bytes": func(v interface{}) string { 380 switch v := v.(type) { 381 case string: 382 f, err := strconv.ParseFloat(v, 64) 383 if err != nil { 384 return err.Error() 385 } 386 return conf.ByteSize(f).String() 387 case int: 388 return conf.ByteSize(v).String() 389 case float64: 390 return conf.ByteSize(v).String() 391 case expr.Number: 392 return conf.ByteSize(v).String() 393 case expr.Scalar: 394 return conf.ByteSize(v).String() 395 } 396 return fmt.Errorf("unexpected type passed to bytes function: %T (%v)", v, v).Error() 397 }, 398 "pct": func(i interface{}) string { 399 return fmt.Sprintf("%.2f%%", i) 400 }, 401 "replace": strings.Replace, 402 "short": func(v string) string { 403 return strings.SplitN(v, ".", 2)[0] 404 }, 405 "html": func(value interface{}) htemplate.HTML { 406 return htemplate.HTML(fmt.Sprint(value)) 407 }, 408 // There is a trap here, this will only make sure that 409 // it is a untyped nil. A typed nil would return true 410 // here. So it is vital that we only return literal 411 // nils from functions when they error with notNil. 412 // This is needed because template's conditionals 413 // treat things like 0 and false as "not true" just like 414 // nil. 415 "notNil": func(value interface{}) bool { 416 if value == nil { 417 return false 418 } 419 return true 420 }, 421 "parseDuration": func(s string) *time.Duration { 422 d, err := time.ParseDuration(s) 423 if err != nil { 424 return nil 425 } 426 return &d 427 }, 428 "append": func(a []interface{}, b interface{}) interface{} { 429 return append(a, b) 430 }, 431 "makeSlice": func(vals ...interface{}) interface{} { 432 return vals 433 }, 434 "makeMap": func(vals ...interface{}) interface{} { 435 if len(vals)%2 != 0 { 436 return fmt.Errorf("MakeMap requires even number of arguments").Error() 437 } 438 m := map[string]interface{}{} 439 for i := 0; i < len(vals); i += 2 { 440 key, ok := vals[i].(string) 441 if !ok { 442 return fmt.Errorf("MakeMap requires all map keys to be strings").Error() 443 } 444 m[key] = vals[i+1] 445 } 446 return m 447 }, 448 "json": func(v interface{}) string { 449 b, err := json.MarshalIndent(v, "", " ") 450 if err != nil { 451 return err.Error() 452 } 453 return string(b) 454 }, 455 "slackLinkButton": func(text, url, style string) interface{} { 456 return slack.Action{ 457 Type: "button", 458 Text: text, 459 URL: url, 460 Style: style, 461 } 462 }, 463 "slackField": func(title string, value interface{}, short bool) interface{} { 464 return slack.Field{ 465 Title: title, 466 Value: value, 467 Short: short, 468 } 469 }, 470 } 471 472 var exRE = regexp.MustCompile(`\$(?:[\w.]+|\{[\w.]+\})`) 473 474 func (c *Conf) Expand(v string, vars map[string]string, ignoreBadExpand bool) string { 475 ss := exRE.ReplaceAllStringFunc(v, func(s string) string { 476 var n string 477 if strings.HasPrefix(s, "${") && strings.HasSuffix(s, "}") && !ignoreBadExpand { 478 s = "$" + s[2:len(s)-1] 479 } 480 if _n, ok := vars[s]; ok { 481 n = _n 482 } else if _n, ok := c.Vars[s]; ok { 483 n = _n 484 } else if strings.HasPrefix(s, "$env.") { 485 n = os.Getenv(s[5:]) 486 } else if strings.HasPrefix(s, "$sys.") { 487 n = c.sysVars[s[5:]] 488 } else if ignoreBadExpand { 489 return s 490 } else { 491 c.errorf("unknown variable %s", s) 492 } 493 return c.Expand(n, vars, ignoreBadExpand) 494 }) 495 return ss 496 } 497 498 func (c *Conf) seen(v string, m map[string]bool) { 499 if m[v] { 500 switch v { 501 case "squelch", "critNotification", "warnNotification", "graphiteHeader": 502 // ignore 503 default: 504 c.errorf("duplicate key: %s", v) 505 } 506 } 507 m[v] = true 508 } 509 510 func (c *Conf) NewExpr(s string) *expr.Expr { 511 exp, err := expr.New(s, c.GetFuncs(c.backends)) 512 if err != nil { 513 c.error(err) 514 } 515 switch exp.Root.Return() { 516 case models.TypeNumberSet, models.TypeScalar: 517 break 518 default: 519 c.errorf("expression must return a number") 520 } 521 return exp 522 } 523 524 func (c *Conf) GetFuncs(backends conf.EnabledBackends) map[string]eparse.Func { 525 lookup := func(e *expr.State, lookup, key string) (results *expr.Results, err error) { 526 results = new(expr.Results) 527 results.IgnoreUnjoined = true 528 l := c.Lookups[lookup] 529 if l == nil { 530 return nil, fmt.Errorf("lookup table not found: %v", lookup) 531 } 532 lookups := l.ToExpr() 533 if lookups == nil { 534 err = fmt.Errorf("lookup table not found: %v", lookup) 535 return 536 } 537 var tags []opentsdb.TagSet 538 for _, tag := range lookups.Tags { 539 var next []opentsdb.TagSet 540 vals, err := e.Search.TagValuesByTagKey(tag, 0) 541 if err != nil { 542 return nil, err 543 } 544 for _, value := range vals { 545 for _, s := range tags { 546 t := s.Copy() 547 t[tag] = value 548 next = append(next, t) 549 } 550 if len(tags) == 0 { 551 next = append(next, opentsdb.TagSet{tag: value}) 552 } 553 } 554 tags = next 555 } 556 for _, tag := range tags { 557 value, ok := lookups.Get(key, tag) 558 if !ok { 559 continue 560 } 561 var num float64 562 num, err = strconv.ParseFloat(value, 64) 563 if err != nil { 564 return nil, err 565 } 566 results.Results = append(results.Results, &expr.Result{ 567 Value: expr.Number(num), 568 Group: tag, 569 }) 570 } 571 return results, nil 572 } 573 lookupSeries := func(e *expr.State, series *expr.Results, lookup, key string) (results *expr.Results, err error) { 574 results = new(expr.Results) 575 results.IgnoreUnjoined = true 576 l := c.Lookups[lookup] 577 if l == nil { 578 return nil, fmt.Errorf("lookup table not found: %v", lookup) 579 } 580 lookups := l.ToExpr() 581 if lookups == nil { 582 err = fmt.Errorf("lookup table not found: %v", lookup) 583 return 584 } 585 for _, res := range series.Results { 586 value, ok := lookups.Get(key, res.Group) 587 if !ok { 588 continue 589 } 590 var num float64 591 num, err = strconv.ParseFloat(value, 64) 592 if err != nil { 593 return nil, err 594 } 595 results.Results = append(results.Results, &expr.Result{ 596 Value: expr.Number(num), 597 Group: res.Group, 598 }) 599 } 600 return results, nil 601 } 602 lookupTags := func(args []eparse.Node) (eparse.Tags, error) { 603 name := args[0].(*eparse.StringNode).Text 604 lookup := c.Lookups[name] 605 if lookup == nil { 606 return nil, fmt.Errorf("bad lookup table %v", name) 607 } 608 t := make(eparse.Tags) 609 for _, v := range lookup.Tags { 610 t[v] = struct{}{} 611 } 612 return t, nil 613 } 614 lookupSeriesTags := func(args []eparse.Node) (eparse.Tags, error) { 615 name := args[1].(*eparse.StringNode).Text 616 lookup := c.Lookups[name] 617 if lookup == nil { 618 return nil, fmt.Errorf("bad lookup table %v", name) 619 } 620 t := make(eparse.Tags) 621 for _, v := range lookup.Tags { 622 t[v] = struct{}{} 623 } 624 return t, nil 625 } 626 627 tagAlert := func(args []eparse.Node) (eparse.Tags, error) { 628 name := args[0].(*eparse.StringNode).Text 629 key := args[1].(*eparse.StringNode).Text 630 a, e, err := c.getAlertExpr(name, key) 631 if err != nil { 632 return nil, err 633 } 634 if a.ReturnType != models.TypeNumberSet { 635 return nil, fmt.Errorf("alert requires a number-returning expression (got %v)", a.ReturnType) 636 } 637 return e.Root.Tags() 638 } 639 640 funcs := map[string]eparse.Func{ 641 "alert": { 642 Args: []models.FuncType{models.TypeString, models.TypeString}, 643 Return: models.TypeNumberSet, 644 Tags: tagAlert, 645 F: c.alert, 646 }, 647 "lookup": { 648 Args: []models.FuncType{models.TypeString, models.TypeString}, 649 Return: models.TypeNumberSet, 650 Tags: lookupTags, 651 F: lookup, 652 }, 653 "lookupSeries": { 654 Args: []models.FuncType{models.TypeSeriesSet, models.TypeString, models.TypeString}, 655 Return: models.TypeNumberSet, 656 Tags: lookupSeriesTags, 657 F: lookupSeries, 658 }, 659 } 660 merge := func(fs map[string]eparse.Func) { 661 for k, v := range fs { 662 funcs[k] = v 663 } 664 } 665 if backends.OpenTSDB { 666 merge(expr.TSDB) 667 } 668 if backends.Graphite { 669 merge(expr.Graphite) 670 } 671 if backends.Elastic { 672 merge(expr.Elastic) 673 } 674 if backends.Influx { 675 merge(expr.Influx) 676 } 677 if backends.Annotate { 678 merge(expr.Annotate) 679 } 680 if backends.AzureMonitor { 681 merge(expr.AzureMonitor) 682 } 683 if backends.Prom { 684 merge(expr.Prom) 685 } 686 if backends.CloudWatch { 687 merge(expr.CloudWatch) 688 } 689 return funcs 690 } 691 692 func (c *Conf) getAlertExpr(name, key string) (*conf.Alert, *expr.Expr, error) { 693 a := c.Alerts[name] 694 if a == nil { 695 return nil, nil, fmt.Errorf("bad alert name %v", name) 696 } 697 var e *expr.Expr 698 switch key { 699 case "crit": 700 e = a.Crit 701 case "warn": 702 e = a.Warn 703 default: 704 return nil, nil, fmt.Errorf("alert: unsupported key %v", key) 705 } 706 if e == nil { 707 return nil, nil, fmt.Errorf("alert: nil expression") 708 } 709 return a, e, nil 710 } 711 712 func (c *Conf) alert(s *expr.State, name, key string) (results *expr.Results, err error) { 713 _, e, err := c.getAlertExpr(name, key) 714 if err != nil { 715 return nil, err 716 } 717 results, _, err = e.ExecuteState(s) 718 if err != nil { 719 return nil, err 720 } 721 if s.History != nil { 722 unknownTags, unevalTags := s.History.GetUnknownAndUnevaluatedAlertKeys(name) 723 // For currently unknown tags NOT in the result set, add an error result 724 for _, ak := range unknownTags { 725 found := false 726 for _, result := range results.Results { 727 if result.Group.Equal(ak.Group()) { 728 found = true 729 break 730 } 731 } 732 if !found { 733 res := expr.Result{ 734 Value: expr.Number(1), 735 Group: ak.Group(), 736 } 737 results.Results = append(results.Results, &res) 738 } 739 } 740 //For all unevaluated tags in run history, make sure we report a nonzero result. 741 for _, ak := range unevalTags { 742 found := false 743 for _, result := range results.Results { 744 if result.Group.Equal(ak.Group()) { 745 result.Value = expr.Number(1) 746 found = true 747 break 748 } 749 } 750 if !found { 751 res := expr.Result{ 752 Value: expr.Number(1), 753 Group: ak.Group(), 754 } 755 results.Results = append(results.Results, &res) 756 } 757 } 758 } 759 return results, nil 760 } 761 762 func (c *Conf) GetTemplate(s string) *conf.Template { 763 return c.Templates[s] 764 } 765 766 func (c *Conf) GetAlerts() map[string]*conf.Alert { 767 return c.Alerts 768 } 769 770 func (c *Conf) GetAlert(s string) *conf.Alert { 771 return c.Alerts[s] 772 } 773 774 func (c *Conf) GetNotifications() map[string]*conf.Notification { 775 return c.Notifications 776 } 777 778 func (c *Conf) GetNotification(s string) *conf.Notification { 779 return c.Notifications[s] 780 } 781 782 func (c *Conf) GetMacro(s string) *conf.Macro { 783 return c.Macros[s] 784 } 785 786 func (c *Conf) GetLookup(s string) *conf.Lookup { 787 return c.Lookups[s] 788 } 789 790 func (c *Conf) GetSquelches() conf.Squelches { 791 return c.Squelch 792 } 793 794 func (c *Conf) GetRawText() string { 795 return c.RawText 796 } 797 798 func (c *Conf) SetReload(reload func() error) { 799 c.reload = reload 800 } 801 802 func (c *Conf) Reload() error { 803 return c.reload() 804 } 805 806 func (c *Conf) SetSaveHook(sh conf.SaveHook) { 807 c.saveHook = sh 808 } 809 810 func (c *Conf) callSaveHook(file, user, message string, args ...string) error { 811 if c.saveHook == nil { 812 return nil 813 } 814 return c.saveHook(file, user, message, args...) 815 } 816 817 func (c *Conf) genHash() { 818 c.Hash = conf.GenHash(c.RawText) 819 } 820 821 func (c *Conf) GetHash() string { 822 return c.Hash 823 } 824 825 // returns any notifications accessible from the alert vis warn/critNotification, including chains and lookups 826 func (c *Conf) getAllPossibleNotifications(a *conf.Alert) map[string]*conf.Notification { 827 nots := map[string]*conf.Notification{} 828 for k, v := range a.WarnNotification.GetAllChained() { 829 nots[k] = v 830 } 831 for k, v := range a.CritNotification.GetAllChained() { 832 nots[k] = v 833 } 834 followLookup := func(l map[string]*conf.Lookup) { 835 for target, lookup := range l { 836 for _, entry := range lookup.Entries { 837 if notNames, ok := entry.Values[target]; ok { 838 for _, k := range strings.Split(notNames, ",") { 839 if not, ok := c.Notifications[k]; ok { 840 nots[k] = not 841 } else { 842 c.errorf("Notification %s needed by lookup %s in %s is not defined.", k, lookup.Name, a.Name) 843 } 844 } 845 } 846 } 847 } 848 } 849 followLookup(a.CritNotification.Lookups) 850 followLookup(a.WarnNotification.Lookups) 851 return nots 852 }