github.com/webonyx/up@v0.7.4-0.20180808230834-91b94e551323/config/alerts.go (about)

     1  package config
     2  
     3  import (
     4  	"strings"
     5  	"time"
     6  
     7  	"github.com/apex/up/internal/util"
     8  	"github.com/apex/up/internal/validate"
     9  	"github.com/pkg/errors"
    10  )
    11  
    12  // types of action.
    13  var types = []string{
    14  	"slack",
    15  	"email",
    16  	"sms",
    17  }
    18  
    19  // namespace mappings.
    20  var namespaceMap = map[string]string{
    21  	"http": "AWS/ApiGateway",
    22  }
    23  
    24  // metrics mappings.
    25  var metricsMap = map[string]string{
    26  	"http.count":   "Count",
    27  	"http.latency": "Latency",
    28  	"http.4xx":     "4XXError",
    29  	"http.5xx":     "5XXError",
    30  }
    31  
    32  // operator mappings.
    33  var operatorMap = map[string]string{
    34  	">":  "GreaterThanThreshold",
    35  	"<":  "LessThanThreshold",
    36  	">=": "GreaterThanOrEqualToThreshold",
    37  	"<=": "LessThanOrEqualToThreshold",
    38  }
    39  
    40  // statistic mappings.
    41  var statisticMap = map[string]string{
    42  	"count":   "SampleCount",
    43  	"sum":     "Sum",
    44  	"avg":     "Average",
    45  	"average": "Average",
    46  	"min":     "Minimum",
    47  	"minimum": "Minimum",
    48  	"max":     "Maximum",
    49  	"maximum": "Maximum",
    50  }
    51  
    52  // missingData options.
    53  var missingData = []string{
    54  	"breaching",
    55  	"notBreaching",
    56  	"ignore",
    57  	"missing",
    58  }
    59  
    60  // AlertAction config.
    61  type AlertAction struct {
    62  	// Name of the action.
    63  	Name string `json:"name"`
    64  
    65  	// Type of action.
    66  	Type string `json:"type"`
    67  
    68  	// Emails for email action.
    69  	Emails []string `json:"emails"`
    70  
    71  	// Numbers for sms action.
    72  	Numbers []string `json:"numbers"`
    73  
    74  	// URL for slack webhook action.
    75  	URL string `json:"url"`
    76  
    77  	// Channel for slack webhook action (optional).
    78  	Channel string `json:"channel"`
    79  
    80  	// Gifs enabled for alerts.
    81  	Gifs bool `json:"gifs"`
    82  }
    83  
    84  // Validate implementation.
    85  func (a *AlertAction) Validate() error {
    86  	if err := validate.RequiredString(a.Name); err != nil {
    87  		return errors.Wrap(err, ".name")
    88  	}
    89  
    90  	if err := validate.List(a.Type, types); err != nil {
    91  		return errors.Wrap(err, ".type")
    92  	}
    93  
    94  	switch a.Type {
    95  	case "email":
    96  		if err := validate.MinStrings(a.Emails, 1); err != nil {
    97  			return errors.Wrap(err, ".emails")
    98  		}
    99  	case "sms":
   100  		if err := validate.MinStrings(a.Numbers, 1); err != nil {
   101  			return errors.Wrap(err, ".numbers")
   102  		}
   103  	case "slack":
   104  		if err := validate.RequiredString(a.URL); err != nil {
   105  			return errors.Wrap(err, ".url")
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  // Alert config.
   113  type Alert struct {
   114  	Description       string   `json:"description"`
   115  	Disable           bool     `json:"disable"`
   116  	Metric            string   `json:"metric"`
   117  	Namespace         string   `json:"namespace"`
   118  	Statistic         string   `json:"statistic"`
   119  	Operator          string   `json:"operator"`
   120  	Threshold         int      `json:"threshold"`
   121  	Period            Duration `json:"period"` // TODO: must be multiple of 60?
   122  	EvaluationPeriods int      `json:"evaluation_periods"`
   123  	Stage             string   `json:"stage"`
   124  	Action            string   `json:"action"`
   125  	Missing           string   `json:"missing"`
   126  }
   127  
   128  // Default implementation.
   129  func (a *Alert) Default() error {
   130  	if a.Operator == "" {
   131  		a.Operator = ">"
   132  	}
   133  
   134  	if a.Missing == "" {
   135  		a.Missing = "notBreaching"
   136  	}
   137  
   138  	if a.Period == 0 {
   139  		a.Period = Duration(time.Minute)
   140  	}
   141  
   142  	if a.EvaluationPeriods == 0 {
   143  		a.EvaluationPeriods = 1
   144  	}
   145  
   146  	if s := a.Metric; s != "" {
   147  		parts := strings.Split(a.Metric, ".")
   148  
   149  		if s, ok := namespaceMap[parts[0]]; ok {
   150  			a.Namespace = s
   151  		}
   152  
   153  		if s, ok := metricsMap[a.Metric]; ok {
   154  			a.Metric = s
   155  		}
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  // Validate implementation.
   162  func (a *Alert) Validate() error {
   163  	if s, ok := operatorMap[a.Operator]; ok {
   164  		a.Operator = s
   165  	} else {
   166  		if err := validate.List(a.Operator, util.StringMapKeys(operatorMap)); err != nil {
   167  			return errors.Wrap(err, ".operator")
   168  		}
   169  	}
   170  
   171  	if s, ok := statisticMap[a.Statistic]; ok {
   172  		a.Statistic = s
   173  	} else {
   174  		if err := validate.List(a.Statistic, util.StringMapKeys(statisticMap)); err != nil {
   175  			return errors.Wrap(err, ".statistic")
   176  		}
   177  	}
   178  
   179  	if err := validate.List(a.Missing, missingData); err != nil {
   180  		return errors.Wrap(err, ".missing")
   181  	}
   182  
   183  	if err := validate.RequiredString(a.Metric); err != nil {
   184  		return errors.Wrap(err, ".metric")
   185  	}
   186  
   187  	if err := validate.RequiredString(a.Namespace); err != nil {
   188  		return errors.Wrap(err, ".namespace")
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  // Alerting config.
   195  type Alerting struct {
   196  	Actions []*AlertAction `json:"actions"`
   197  	Alerts  []*Alert       `json:"alerts"`
   198  }
   199  
   200  // Default implementation.
   201  func (a *Alerting) Default() error {
   202  	for i, a := range a.Alerts {
   203  		if err := a.Default(); err != nil {
   204  			return errors.Wrapf(err, ".actions %d", i)
   205  		}
   206  	}
   207  
   208  	return nil
   209  }
   210  
   211  // Validate implementation.
   212  func (a *Alerting) Validate() error {
   213  	for i, a := range a.Actions {
   214  		if err := a.Validate(); err != nil {
   215  			return errors.Wrapf(err, ".actions %d", i)
   216  		}
   217  	}
   218  
   219  	for i, alert := range a.Alerts {
   220  		if err := alert.Validate(); err != nil {
   221  			return errors.Wrapf(err, ".actions %d", i)
   222  		}
   223  
   224  		if a.GetActionByName(alert.Action) == nil {
   225  			return errors.Errorf(".action %q is not defined", alert.Action)
   226  		}
   227  	}
   228  
   229  	return nil
   230  }
   231  
   232  // GetActionByName returns the action by name or nil.
   233  func (a *Alerting) GetActionByName(name string) *AlertAction {
   234  	for _, action := range a.Actions {
   235  		if action.Name == name {
   236  			return action
   237  		}
   238  	}
   239  
   240  	return nil
   241  }