382 lines
13 KiB
Go
382 lines
13 KiB
Go
package circonus
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/circonus-labs/circonus-gometrics/api"
|
|
"github.com/circonus-labs/circonus-gometrics/api/config"
|
|
"github.com/hashicorp/errwrap"
|
|
)
|
|
|
|
var knownCheckTypes map[circonusCheckType]struct{}
|
|
var knownContactMethods map[contactMethods]struct{}
|
|
|
|
var userContactMethods map[contactMethods]struct{}
|
|
var externalContactMethods map[contactMethods]struct{}
|
|
var supportedHTTPVersions = validStringValues{"0.9", "1.0", "1.1", "2.0"}
|
|
var supportedMetricClusterTypes = validStringValues{
|
|
"average", "count", "counter", "counter2", "counter2_stddev",
|
|
"counter_stddev", "derive", "derive2", "derive2_stddev", "derive_stddev",
|
|
"histogram", "stddev", "text",
|
|
}
|
|
|
|
func init() {
|
|
checkTypes := []circonusCheckType{
|
|
"caql", "cim", "circonuswindowsagent", "circonuswindowsagent,nad",
|
|
"collectd", "composite", "dcm", "dhcp", "dns", "elasticsearch",
|
|
"external", "ganglia", "googleanalytics", "haproxy", "http",
|
|
"http,apache", "httptrap", "imap", "jmx", "json", "json,couchdb",
|
|
"json,mongodb", "json,nad", "json,riak", "ldap", "memcached",
|
|
"munin", "mysql", "newrelic_rpm", "nginx", "nrpe", "ntp",
|
|
"oracle", "ping_icmp", "pop3", "postgres", "redis", "resmon",
|
|
"smtp", "snmp", "snmp,momentum", "sqlserver", "ssh2", "statsd",
|
|
"tcp", "varnish", "keynote", "keynote_pulse", "cloudwatch",
|
|
"ec_console", "mongodb",
|
|
}
|
|
|
|
knownCheckTypes = make(map[circonusCheckType]struct{}, len(checkTypes))
|
|
for _, k := range checkTypes {
|
|
knownCheckTypes[k] = struct{}{}
|
|
}
|
|
|
|
userMethods := []contactMethods{"email", "sms", "xmpp"}
|
|
externalMethods := []contactMethods{"slack"}
|
|
|
|
knownContactMethods = make(map[contactMethods]struct{}, len(externalContactMethods)+len(userContactMethods))
|
|
|
|
externalContactMethods = make(map[contactMethods]struct{}, len(externalMethods))
|
|
for _, k := range externalMethods {
|
|
knownContactMethods[k] = struct{}{}
|
|
externalContactMethods[k] = struct{}{}
|
|
}
|
|
|
|
userContactMethods = make(map[contactMethods]struct{}, len(userMethods))
|
|
for _, k := range userMethods {
|
|
knownContactMethods[k] = struct{}{}
|
|
userContactMethods[k] = struct{}{}
|
|
}
|
|
}
|
|
|
|
func validateCheckType(v interface{}, key string) (warnings []string, errors []error) {
|
|
if _, ok := knownCheckTypes[circonusCheckType(v.(string))]; !ok {
|
|
warnings = append(warnings, fmt.Sprintf("Possibly unsupported check type: %s", v.(string)))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
|
|
func validateCheckCloudWatchDimmensions(v interface{}, key string) (warnings []string, errors []error) {
|
|
validDimmensionName := regexp.MustCompile(`^[\S]+$`)
|
|
validDimmensionValue := regexp.MustCompile(`^[\S]+$`)
|
|
|
|
dimmensions := v.(map[string]interface{})
|
|
for k, vRaw := range dimmensions {
|
|
if !validDimmensionName.MatchString(k) {
|
|
errors = append(errors, fmt.Errorf("Invalid CloudWatch Dimmension Name specified: %q", k))
|
|
continue
|
|
}
|
|
|
|
v := vRaw.(string)
|
|
if !validDimmensionValue.MatchString(v) {
|
|
errors = append(errors, fmt.Errorf("Invalid value for CloudWatch Dimmension %q specified: %q", k, v))
|
|
}
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
|
|
func validateContactGroup(cg *api.ContactGroup) error {
|
|
for i := range cg.Reminders {
|
|
if cg.Reminders[i] != 0 && cg.AggregationWindow > cg.Reminders[i] {
|
|
return fmt.Errorf("severity %d reminder (%ds) is shorter than the aggregation window (%ds)", i+1, cg.Reminders[i], cg.AggregationWindow)
|
|
}
|
|
}
|
|
|
|
for severityIndex := range cg.Escalations {
|
|
switch {
|
|
case cg.Escalations[severityIndex] == nil:
|
|
continue
|
|
case cg.Escalations[severityIndex].After > 0 && cg.Escalations[severityIndex].ContactGroupCID == "",
|
|
cg.Escalations[severityIndex].After == 0 && cg.Escalations[severityIndex].ContactGroupCID != "":
|
|
return fmt.Errorf("severity %d escalation requires both and %s and %s be set", severityIndex+1, contactEscalateToAttr, contactEscalateAfterAttr)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func validateContactGroupCID(attrName schemaAttr) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
validContactGroupCID := regexp.MustCompile(config.ContactGroupCIDRegex)
|
|
|
|
if !validContactGroupCID.MatchString(v.(string)) {
|
|
errors = append(errors, fmt.Errorf("Invalid %s specified (%q)", attrName, v.(string)))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
}
|
|
|
|
func validateDurationMin(attrName schemaAttr, minDuration string) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
var min time.Duration
|
|
{
|
|
var err error
|
|
min, err = time.ParseDuration(minDuration)
|
|
if err != nil {
|
|
return func(interface{}, string) (warnings []string, errors []error) {
|
|
errors = []error{errwrap.Wrapf(fmt.Sprintf("Invalid time +%q: {{err}}", minDuration), err)}
|
|
return warnings, errors
|
|
}
|
|
}
|
|
}
|
|
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
d, err := time.ParseDuration(v.(string))
|
|
switch {
|
|
case err != nil:
|
|
errors = append(errors, errwrap.Wrapf(fmt.Sprintf("Invalid %s specified (%q): {{err}}", attrName, v.(string)), err))
|
|
case d < min:
|
|
errors = append(errors, fmt.Errorf("Invalid %s specified (%q): minimum value must be %s", attrName, v.(string), min))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
}
|
|
|
|
func validateDurationMax(attrName schemaAttr, maxDuration string) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
var max time.Duration
|
|
{
|
|
var err error
|
|
max, err = time.ParseDuration(maxDuration)
|
|
if err != nil {
|
|
return func(interface{}, string) (warnings []string, errors []error) {
|
|
errors = []error{errwrap.Wrapf(fmt.Sprintf("Invalid time +%q: {{err}}", maxDuration), err)}
|
|
return warnings, errors
|
|
}
|
|
}
|
|
}
|
|
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
d, err := time.ParseDuration(v.(string))
|
|
switch {
|
|
case err != nil:
|
|
errors = append(errors, errwrap.Wrapf(fmt.Sprintf("Invalid %s specified (%q): {{err}}", attrName, v.(string)), err))
|
|
case d > max:
|
|
errors = append(errors, fmt.Errorf("Invalid %s specified (%q): maximum value must be less than or equal to %s", attrName, v.(string), max))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
}
|
|
|
|
func validateFloatMin(attrName schemaAttr, min float64) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
if v.(float64) < min {
|
|
errors = append(errors, fmt.Errorf("Invalid %s specified (%f): minimum value must be %f", attrName, v.(float64), min))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
}
|
|
|
|
func validateFloatMax(attrName schemaAttr, max float64) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
if v.(float64) > max {
|
|
errors = append(errors, fmt.Errorf("Invalid %s specified (%f): maximum value must be %f", attrName, v.(float64), max))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
}
|
|
|
|
// validateFuncs takes a list of functions and runs them in serial until either
|
|
// a warning or error is returned from the first validation function argument.
|
|
func validateFuncs(fns ...func(v interface{}, key string) (warnings []string, errors []error)) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
for _, fn := range fns {
|
|
warnings, errors = fn(v, key)
|
|
if len(warnings) > 0 || len(errors) > 0 {
|
|
break
|
|
}
|
|
}
|
|
return warnings, errors
|
|
}
|
|
}
|
|
|
|
func validateHTTPHeaders(v interface{}, key string) (warnings []string, errors []error) {
|
|
validHTTPHeader := regexp.MustCompile(`.+`)
|
|
validHTTPValue := regexp.MustCompile(`.+`)
|
|
|
|
headers := v.(map[string]interface{})
|
|
for k, vRaw := range headers {
|
|
if !validHTTPHeader.MatchString(k) {
|
|
errors = append(errors, fmt.Errorf("Invalid HTTP Header specified: %q", k))
|
|
continue
|
|
}
|
|
|
|
v := vRaw.(string)
|
|
if !validHTTPValue.MatchString(v) {
|
|
errors = append(errors, fmt.Errorf("Invalid value for HTTP Header %q specified: %q", k, v))
|
|
}
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
|
|
func validateGraphAxisOptions(v interface{}, key string) (warnings []string, errors []error) {
|
|
axisOptionsMap := v.(map[string]interface{})
|
|
validOpts := map[schemaAttr]struct{}{
|
|
graphAxisLogarithmicAttr: struct{}{},
|
|
graphAxisMaxAttr: struct{}{},
|
|
graphAxisMinAttr: struct{}{},
|
|
}
|
|
|
|
for k := range axisOptionsMap {
|
|
if _, ok := validOpts[schemaAttr(k)]; !ok {
|
|
errors = append(errors, fmt.Errorf("Invalid axis option specified: %q", k))
|
|
continue
|
|
}
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
|
|
func validateIntMin(attrName schemaAttr, min int) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
if v.(int) < min {
|
|
errors = append(errors, fmt.Errorf("Invalid %s specified (%d): minimum value must be %d", attrName, v.(int), min))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
}
|
|
|
|
func validateIntMax(attrName schemaAttr, max int) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
if v.(int) > max {
|
|
errors = append(errors, fmt.Errorf("Invalid %s specified (%d): maximum value must be %d", attrName, v.(int), max))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
}
|
|
|
|
func validateMetricType(v interface{}, key string) (warnings []string, errors []error) {
|
|
value := v.(string)
|
|
switch value {
|
|
case "caql", "composite", "histogram", "numeric", "text":
|
|
default:
|
|
errors = append(errors, fmt.Errorf("unsupported metric type %s", value))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
|
|
func validateRegexp(attrName schemaAttr, reString string) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
re := regexp.MustCompile(reString)
|
|
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
if !re.MatchString(v.(string)) {
|
|
errors = append(errors, fmt.Errorf("Invalid %s specified (%q): regexp failed to match string", attrName, v.(string)))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
}
|
|
|
|
func validateTag(v interface{}, key string) (warnings []string, errors []error) {
|
|
tag := v.(string)
|
|
if !strings.ContainsRune(tag, ':') {
|
|
errors = append(errors, fmt.Errorf("tag %q is missing a category", tag))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
|
|
func validateUserCID(attrName string) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
valid := regexp.MustCompile(config.UserCIDRegex)
|
|
|
|
if !valid.MatchString(v.(string)) {
|
|
errors = append(errors, fmt.Errorf("Invalid %s specified (%q)", attrName, v.(string)))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
}
|
|
|
|
type urlParseFlags int
|
|
|
|
const (
|
|
urlIsAbs urlParseFlags = 1 << iota
|
|
urlOptional
|
|
urlWithoutPath
|
|
urlWithoutPort
|
|
urlWithoutSchema
|
|
)
|
|
|
|
const urlBasicCheck urlParseFlags = 0
|
|
|
|
func validateHTTPURL(attrName schemaAttr, checkFlags urlParseFlags) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
s := v.(string)
|
|
if checkFlags&urlOptional != 0 && s == "" {
|
|
return warnings, errors
|
|
}
|
|
|
|
u, err := url.Parse(v.(string))
|
|
switch {
|
|
case err != nil:
|
|
errors = append(errors, errwrap.Wrapf(fmt.Sprintf("Invalid %s specified (%q): {{err}}", attrName, v.(string)), err))
|
|
case u.Host == "":
|
|
errors = append(errors, fmt.Errorf("Invalid %s specified: host can not be empty", attrName))
|
|
case !(u.Scheme == "http" || u.Scheme == "https"):
|
|
errors = append(errors, fmt.Errorf("Invalid %s specified: scheme unsupported (only support http and https)", attrName))
|
|
}
|
|
|
|
if checkFlags&urlIsAbs != 0 && !u.IsAbs() {
|
|
errors = append(errors, fmt.Errorf("Schema is missing from URL %q (HINT: https://%s)", v.(string), v.(string)))
|
|
}
|
|
|
|
if checkFlags&urlWithoutSchema != 0 && u.IsAbs() {
|
|
errors = append(errors, fmt.Errorf("Schema is present on URL %q (HINT: drop the https://%s)", v.(string), v.(string)))
|
|
}
|
|
|
|
if checkFlags&urlWithoutPath != 0 && u.Path != "" {
|
|
errors = append(errors, fmt.Errorf("Path is present on URL %q (HINT: drop the %s)", v.(string), u.Path))
|
|
}
|
|
|
|
if checkFlags&urlWithoutPort != 0 {
|
|
hostParts := strings.SplitN(u.Host, ":", 2)
|
|
if len(hostParts) != 1 {
|
|
errors = append(errors, fmt.Errorf("Port is present on URL %q (HINT: drop the :%s)", v.(string), hostParts[1]))
|
|
}
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
}
|
|
|
|
func validateStringIn(attrName schemaAttr, valid validStringValues) func(v interface{}, key string) (warnings []string, errors []error) {
|
|
return func(v interface{}, key string) (warnings []string, errors []error) {
|
|
s := v.(string)
|
|
var found bool
|
|
for i := range valid {
|
|
if s == string(valid[i]) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
errors = append(errors, fmt.Errorf("Invalid %q specified: %q not found in list %#v", string(attrName), s, valid))
|
|
}
|
|
|
|
return warnings, errors
|
|
}
|
|
}
|