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  }