github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/circonus/validators.go (about) 1 package circonus 2 3 import ( 4 "fmt" 5 "net/url" 6 "regexp" 7 "strings" 8 "time" 9 10 "github.com/circonus-labs/circonus-gometrics/api" 11 "github.com/circonus-labs/circonus-gometrics/api/config" 12 "github.com/hashicorp/errwrap" 13 ) 14 15 var knownCheckTypes map[circonusCheckType]struct{} 16 var knownContactMethods map[contactMethods]struct{} 17 18 var userContactMethods map[contactMethods]struct{} 19 var externalContactMethods map[contactMethods]struct{} 20 var supportedHTTPVersions = validStringValues{"0.9", "1.0", "1.1", "2.0"} 21 var supportedMetricClusterTypes = validStringValues{ 22 "average", "count", "counter", "counter2", "counter2_stddev", 23 "counter_stddev", "derive", "derive2", "derive2_stddev", "derive_stddev", 24 "histogram", "stddev", "text", 25 } 26 27 func init() { 28 checkTypes := []circonusCheckType{ 29 "caql", "cim", "circonuswindowsagent", "circonuswindowsagent,nad", 30 "collectd", "composite", "dcm", "dhcp", "dns", "elasticsearch", 31 "external", "ganglia", "googleanalytics", "haproxy", "http", 32 "http,apache", "httptrap", "imap", "jmx", "json", "json,couchdb", 33 "json,mongodb", "json,nad", "json,riak", "ldap", "memcached", 34 "munin", "mysql", "newrelic_rpm", "nginx", "nrpe", "ntp", 35 "oracle", "ping_icmp", "pop3", "postgres", "redis", "resmon", 36 "smtp", "snmp", "snmp,momentum", "sqlserver", "ssh2", "statsd", 37 "tcp", "varnish", "keynote", "keynote_pulse", "cloudwatch", 38 "ec_console", "mongodb", 39 } 40 41 knownCheckTypes = make(map[circonusCheckType]struct{}, len(checkTypes)) 42 for _, k := range checkTypes { 43 knownCheckTypes[k] = struct{}{} 44 } 45 46 userMethods := []contactMethods{"email", "sms", "xmpp"} 47 externalMethods := []contactMethods{"slack"} 48 49 knownContactMethods = make(map[contactMethods]struct{}, len(externalContactMethods)+len(userContactMethods)) 50 51 externalContactMethods = make(map[contactMethods]struct{}, len(externalMethods)) 52 for _, k := range externalMethods { 53 knownContactMethods[k] = struct{}{} 54 externalContactMethods[k] = struct{}{} 55 } 56 57 userContactMethods = make(map[contactMethods]struct{}, len(userMethods)) 58 for _, k := range userMethods { 59 knownContactMethods[k] = struct{}{} 60 userContactMethods[k] = struct{}{} 61 } 62 } 63 64 func validateCheckType(v interface{}, key string) (warnings []string, errors []error) { 65 if _, ok := knownCheckTypes[circonusCheckType(v.(string))]; !ok { 66 warnings = append(warnings, fmt.Sprintf("Possibly unsupported check type: %s", v.(string))) 67 } 68 69 return warnings, errors 70 } 71 72 func validateCheckCloudWatchDimmensions(v interface{}, key string) (warnings []string, errors []error) { 73 validDimmensionName := regexp.MustCompile(`^[\S]+$`) 74 validDimmensionValue := regexp.MustCompile(`^[\S]+$`) 75 76 dimmensions := v.(map[string]interface{}) 77 for k, vRaw := range dimmensions { 78 if !validDimmensionName.MatchString(k) { 79 errors = append(errors, fmt.Errorf("Invalid CloudWatch Dimmension Name specified: %q", k)) 80 continue 81 } 82 83 v := vRaw.(string) 84 if !validDimmensionValue.MatchString(v) { 85 errors = append(errors, fmt.Errorf("Invalid value for CloudWatch Dimmension %q specified: %q", k, v)) 86 } 87 } 88 89 return warnings, errors 90 } 91 92 func validateContactGroup(cg *api.ContactGroup) error { 93 for i := range cg.Reminders { 94 if cg.Reminders[i] != 0 && cg.AggregationWindow > cg.Reminders[i] { 95 return fmt.Errorf("severity %d reminder (%ds) is shorter than the aggregation window (%ds)", i+1, cg.Reminders[i], cg.AggregationWindow) 96 } 97 } 98 99 for severityIndex := range cg.Escalations { 100 switch { 101 case cg.Escalations[severityIndex] == nil: 102 continue 103 case cg.Escalations[severityIndex].After > 0 && cg.Escalations[severityIndex].ContactGroupCID == "", 104 cg.Escalations[severityIndex].After == 0 && cg.Escalations[severityIndex].ContactGroupCID != "": 105 return fmt.Errorf("severity %d escalation requires both and %s and %s be set", severityIndex+1, contactEscalateToAttr, contactEscalateAfterAttr) 106 } 107 } 108 109 return nil 110 } 111 112 func validateContactGroupCID(attrName schemaAttr) func(v interface{}, key string) (warnings []string, errors []error) { 113 return func(v interface{}, key string) (warnings []string, errors []error) { 114 validContactGroupCID := regexp.MustCompile(config.ContactGroupCIDRegex) 115 116 if !validContactGroupCID.MatchString(v.(string)) { 117 errors = append(errors, fmt.Errorf("Invalid %s specified (%q)", attrName, v.(string))) 118 } 119 120 return warnings, errors 121 } 122 } 123 124 func validateDurationMin(attrName schemaAttr, minDuration string) func(v interface{}, key string) (warnings []string, errors []error) { 125 var min time.Duration 126 { 127 var err error 128 min, err = time.ParseDuration(minDuration) 129 if err != nil { 130 return func(interface{}, string) (warnings []string, errors []error) { 131 errors = []error{errwrap.Wrapf(fmt.Sprintf("Invalid time +%q: {{err}}", minDuration), err)} 132 return warnings, errors 133 } 134 } 135 } 136 137 return func(v interface{}, key string) (warnings []string, errors []error) { 138 d, err := time.ParseDuration(v.(string)) 139 switch { 140 case err != nil: 141 errors = append(errors, errwrap.Wrapf(fmt.Sprintf("Invalid %s specified (%q): {{err}}", attrName, v.(string)), err)) 142 case d < min: 143 errors = append(errors, fmt.Errorf("Invalid %s specified (%q): minimum value must be %s", attrName, v.(string), min)) 144 } 145 146 return warnings, errors 147 } 148 } 149 150 func validateDurationMax(attrName schemaAttr, maxDuration string) func(v interface{}, key string) (warnings []string, errors []error) { 151 var max time.Duration 152 { 153 var err error 154 max, err = time.ParseDuration(maxDuration) 155 if err != nil { 156 return func(interface{}, string) (warnings []string, errors []error) { 157 errors = []error{errwrap.Wrapf(fmt.Sprintf("Invalid time +%q: {{err}}", maxDuration), err)} 158 return warnings, errors 159 } 160 } 161 } 162 163 return func(v interface{}, key string) (warnings []string, errors []error) { 164 d, err := time.ParseDuration(v.(string)) 165 switch { 166 case err != nil: 167 errors = append(errors, errwrap.Wrapf(fmt.Sprintf("Invalid %s specified (%q): {{err}}", attrName, v.(string)), err)) 168 case d > max: 169 errors = append(errors, fmt.Errorf("Invalid %s specified (%q): maximum value must be less than or equal to %s", attrName, v.(string), max)) 170 } 171 172 return warnings, errors 173 } 174 } 175 176 func validateFloatMin(attrName schemaAttr, min float64) func(v interface{}, key string) (warnings []string, errors []error) { 177 return func(v interface{}, key string) (warnings []string, errors []error) { 178 if v.(float64) < min { 179 errors = append(errors, fmt.Errorf("Invalid %s specified (%f): minimum value must be %f", attrName, v.(float64), min)) 180 } 181 182 return warnings, errors 183 } 184 } 185 186 func validateFloatMax(attrName schemaAttr, max float64) func(v interface{}, key string) (warnings []string, errors []error) { 187 return func(v interface{}, key string) (warnings []string, errors []error) { 188 if v.(float64) > max { 189 errors = append(errors, fmt.Errorf("Invalid %s specified (%f): maximum value must be %f", attrName, v.(float64), max)) 190 } 191 192 return warnings, errors 193 } 194 } 195 196 // validateFuncs takes a list of functions and runs them in serial until either 197 // a warning or error is returned from the first validation function argument. 198 func validateFuncs(fns ...func(v interface{}, key string) (warnings []string, errors []error)) func(v interface{}, key string) (warnings []string, errors []error) { 199 return func(v interface{}, key string) (warnings []string, errors []error) { 200 for _, fn := range fns { 201 warnings, errors = fn(v, key) 202 if len(warnings) > 0 || len(errors) > 0 { 203 break 204 } 205 } 206 return warnings, errors 207 } 208 } 209 210 func validateHTTPHeaders(v interface{}, key string) (warnings []string, errors []error) { 211 validHTTPHeader := regexp.MustCompile(`.+`) 212 validHTTPValue := regexp.MustCompile(`.+`) 213 214 headers := v.(map[string]interface{}) 215 for k, vRaw := range headers { 216 if !validHTTPHeader.MatchString(k) { 217 errors = append(errors, fmt.Errorf("Invalid HTTP Header specified: %q", k)) 218 continue 219 } 220 221 v := vRaw.(string) 222 if !validHTTPValue.MatchString(v) { 223 errors = append(errors, fmt.Errorf("Invalid value for HTTP Header %q specified: %q", k, v)) 224 } 225 } 226 227 return warnings, errors 228 } 229 230 func validateGraphAxisOptions(v interface{}, key string) (warnings []string, errors []error) { 231 axisOptionsMap := v.(map[string]interface{}) 232 validOpts := map[schemaAttr]struct{}{ 233 graphAxisLogarithmicAttr: struct{}{}, 234 graphAxisMaxAttr: struct{}{}, 235 graphAxisMinAttr: struct{}{}, 236 } 237 238 for k := range axisOptionsMap { 239 if _, ok := validOpts[schemaAttr(k)]; !ok { 240 errors = append(errors, fmt.Errorf("Invalid axis option specified: %q", k)) 241 continue 242 } 243 } 244 245 return warnings, errors 246 } 247 248 func validateIntMin(attrName schemaAttr, min int) func(v interface{}, key string) (warnings []string, errors []error) { 249 return func(v interface{}, key string) (warnings []string, errors []error) { 250 if v.(int) < min { 251 errors = append(errors, fmt.Errorf("Invalid %s specified (%d): minimum value must be %d", attrName, v.(int), min)) 252 } 253 254 return warnings, errors 255 } 256 } 257 258 func validateIntMax(attrName schemaAttr, max int) func(v interface{}, key string) (warnings []string, errors []error) { 259 return func(v interface{}, key string) (warnings []string, errors []error) { 260 if v.(int) > max { 261 errors = append(errors, fmt.Errorf("Invalid %s specified (%d): maximum value must be %d", attrName, v.(int), max)) 262 } 263 264 return warnings, errors 265 } 266 } 267 268 func validateMetricType(v interface{}, key string) (warnings []string, errors []error) { 269 value := v.(string) 270 switch value { 271 case "caql", "composite", "histogram", "numeric", "text": 272 default: 273 errors = append(errors, fmt.Errorf("unsupported metric type %s", value)) 274 } 275 276 return warnings, errors 277 } 278 279 func validateRegexp(attrName schemaAttr, reString string) func(v interface{}, key string) (warnings []string, errors []error) { 280 re := regexp.MustCompile(reString) 281 282 return func(v interface{}, key string) (warnings []string, errors []error) { 283 if !re.MatchString(v.(string)) { 284 errors = append(errors, fmt.Errorf("Invalid %s specified (%q): regexp failed to match string", attrName, v.(string))) 285 } 286 287 return warnings, errors 288 } 289 } 290 291 func validateTag(v interface{}, key string) (warnings []string, errors []error) { 292 tag := v.(string) 293 if !strings.ContainsRune(tag, ':') { 294 errors = append(errors, fmt.Errorf("tag %q is missing a category", tag)) 295 } 296 297 return warnings, errors 298 } 299 300 func validateUserCID(attrName string) func(v interface{}, key string) (warnings []string, errors []error) { 301 return func(v interface{}, key string) (warnings []string, errors []error) { 302 valid := regexp.MustCompile(config.UserCIDRegex) 303 304 if !valid.MatchString(v.(string)) { 305 errors = append(errors, fmt.Errorf("Invalid %s specified (%q)", attrName, v.(string))) 306 } 307 308 return warnings, errors 309 } 310 } 311 312 type urlParseFlags int 313 314 const ( 315 urlIsAbs urlParseFlags = 1 << iota 316 urlOptional 317 urlWithoutPath 318 urlWithoutPort 319 urlWithoutSchema 320 ) 321 322 const urlBasicCheck urlParseFlags = 0 323 324 func validateHTTPURL(attrName schemaAttr, checkFlags urlParseFlags) func(v interface{}, key string) (warnings []string, errors []error) { 325 return func(v interface{}, key string) (warnings []string, errors []error) { 326 s := v.(string) 327 if checkFlags&urlOptional != 0 && s == "" { 328 return warnings, errors 329 } 330 331 u, err := url.Parse(v.(string)) 332 switch { 333 case err != nil: 334 errors = append(errors, errwrap.Wrapf(fmt.Sprintf("Invalid %s specified (%q): {{err}}", attrName, v.(string)), err)) 335 case u.Host == "": 336 errors = append(errors, fmt.Errorf("Invalid %s specified: host can not be empty", attrName)) 337 case !(u.Scheme == "http" || u.Scheme == "https"): 338 errors = append(errors, fmt.Errorf("Invalid %s specified: scheme unsupported (only support http and https)", attrName)) 339 } 340 341 if checkFlags&urlIsAbs != 0 && !u.IsAbs() { 342 errors = append(errors, fmt.Errorf("Schema is missing from URL %q (HINT: https://%s)", v.(string), v.(string))) 343 } 344 345 if checkFlags&urlWithoutSchema != 0 && u.IsAbs() { 346 errors = append(errors, fmt.Errorf("Schema is present on URL %q (HINT: drop the https://%s)", v.(string), v.(string))) 347 } 348 349 if checkFlags&urlWithoutPath != 0 && u.Path != "" { 350 errors = append(errors, fmt.Errorf("Path is present on URL %q (HINT: drop the %s)", v.(string), u.Path)) 351 } 352 353 if checkFlags&urlWithoutPort != 0 { 354 hostParts := strings.SplitN(u.Host, ":", 2) 355 if len(hostParts) != 1 { 356 errors = append(errors, fmt.Errorf("Port is present on URL %q (HINT: drop the :%s)", v.(string), hostParts[1])) 357 } 358 } 359 360 return warnings, errors 361 } 362 } 363 364 func validateStringIn(attrName schemaAttr, valid validStringValues) func(v interface{}, key string) (warnings []string, errors []error) { 365 return func(v interface{}, key string) (warnings []string, errors []error) { 366 s := v.(string) 367 var found bool 368 for i := range valid { 369 if s == string(valid[i]) { 370 found = true 371 break 372 } 373 } 374 375 if !found { 376 errors = append(errors, fmt.Errorf("Invalid %q specified: %q not found in list %#v", string(attrName), s, valid)) 377 } 378 379 return warnings, errors 380 } 381 }