github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/circonus/resource_circonus_rule_set.go (about) 1 package circonus 2 3 import ( 4 "bytes" 5 "fmt" 6 "sort" 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 "github.com/hashicorp/terraform/helper/hashcode" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 const ( 18 // circonus_rule_set.* resource attribute names 19 ruleSetCheckAttr = "check" 20 ruleSetIfAttr = "if" 21 ruleSetLinkAttr = "link" 22 ruleSetMetricTypeAttr = "metric_type" 23 ruleSetNotesAttr = "notes" 24 ruleSetParentAttr = "parent" 25 ruleSetMetricNameAttr = "metric_name" 26 ruleSetTagsAttr = "tags" 27 28 // circonus_rule_set.if.* resource attribute names 29 ruleSetThenAttr = "then" 30 ruleSetValueAttr = "value" 31 32 // circonus_rule_set.if.then.* resource attribute names 33 ruleSetAfterAttr = "after" 34 ruleSetNotifyAttr = "notify" 35 ruleSetSeverityAttr = "severity" 36 37 // circonus_rule_set.if.value.* resource attribute names 38 ruleSetAbsentAttr = "absent" // apiRuleSetAbsent 39 ruleSetChangedAttr = "changed" // apiRuleSetChanged 40 ruleSetContainsAttr = "contains" // apiRuleSetContains 41 ruleSetMatchAttr = "match" // apiRuleSetMatch 42 ruleSetMaxValueAttr = "max_value" // apiRuleSetMaxValue 43 ruleSetMinValueAttr = "min_value" // apiRuleSetMinValue 44 ruleSetNotContainAttr = "not_contain" // apiRuleSetNotContains 45 ruleSetNotMatchAttr = "not_match" // apiRuleSetNotMatch 46 ruleSetOverAttr = "over" 47 48 // circonus_rule_set.if.value.over.* resource attribute names 49 ruleSetLastAttr = "last" 50 ruleSetUsingAttr = "using" 51 ) 52 53 const ( 54 // Different criteria that an api.RuleSetRule can return 55 apiRuleSetAbsent = "on absence" // ruleSetAbsentAttr 56 apiRuleSetChanged = "on change" // ruleSetChangedAttr 57 apiRuleSetContains = "contains" // ruleSetContainsAttr 58 apiRuleSetMatch = "match" // ruleSetMatchAttr 59 apiRuleSetMaxValue = "max value" // ruleSetMaxValueAttr 60 apiRuleSetMinValue = "min value" // ruleSetMinValueAttr 61 apiRuleSetNotContains = "does not contain" // ruleSetNotContainAttr 62 apiRuleSetNotMatch = "does not match" // ruleSetNotMatchAttr 63 ) 64 65 var ruleSetDescriptions = attrDescrs{ 66 // circonus_rule_set.* resource attribute names 67 ruleSetCheckAttr: "The CID of the check that contains the metric for this rule set", 68 ruleSetIfAttr: "A rule to execute for this rule set", 69 ruleSetLinkAttr: "URL to show users when this rule set is active (e.g. wiki)", 70 ruleSetMetricTypeAttr: "The type of data flowing through the specified metric stream", 71 ruleSetNotesAttr: "Notes describing this rule set", 72 ruleSetParentAttr: "Parent CID that must be healthy for this rule set to be active", 73 ruleSetMetricNameAttr: "The name of the metric stream within a check to register the rule set with", 74 ruleSetTagsAttr: "Tags associated with this rule set", 75 } 76 77 var ruleSetIfDescriptions = attrDescrs{ 78 // circonus_rule_set.if.* resource attribute names 79 ruleSetThenAttr: "Description of the action(s) to take when this rule set is active", 80 ruleSetValueAttr: "Predicate that the rule set uses to evaluate a stream of metrics", 81 } 82 83 var ruleSetIfValueDescriptions = attrDescrs{ 84 // circonus_rule_set.if.value.* resource attribute names 85 ruleSetAbsentAttr: "Fire the rule set if there has been no data for the given metric stream over the last duration", 86 ruleSetChangedAttr: "Boolean indicating the value has changed", 87 ruleSetContainsAttr: "Fire the rule set if the text metric contain the following string", 88 ruleSetMatchAttr: "Fire the rule set if the text metric exactly match the following string", 89 ruleSetNotMatchAttr: "Fire the rule set if the text metric not match the following string", 90 ruleSetMinValueAttr: "Fire the rule set if the numeric value less than the specified value", 91 ruleSetNotContainAttr: "Fire the rule set if the text metric does not contain the following string", 92 ruleSetMaxValueAttr: "Fire the rule set if the numeric value is more than the specified value", 93 ruleSetOverAttr: "Use a derived value using a window", 94 ruleSetThenAttr: "Action to take when the rule set is active", 95 } 96 97 var ruleSetIfValueOverDescriptions = attrDescrs{ 98 // circonus_rule_set.if.value.over.* resource attribute names 99 ruleSetLastAttr: "Duration over which data from the last interval is examined", 100 ruleSetUsingAttr: "Define the window funciton to use over the last duration", 101 } 102 103 var ruleSetIfThenDescriptions = attrDescrs{ 104 // circonus_rule_set.if.then.* resource attribute names 105 ruleSetAfterAttr: "The length of time we should wait before contacting the contact groups after this ruleset has faulted.", 106 ruleSetNotifyAttr: "List of contact groups to notify at the following appropriate severity if this rule set is active.", 107 ruleSetSeverityAttr: "Send a notification at this severity level.", 108 } 109 110 func resourceRuleSet() *schema.Resource { 111 makeConflictsWith := func(in ...schemaAttr) []string { 112 out := make([]string, 0, len(in)) 113 for _, attr := range in { 114 out = append(out, string(ruleSetIfAttr)+"."+string(ruleSetValueAttr)+"."+string(attr)) 115 } 116 return out 117 } 118 119 return &schema.Resource{ 120 Create: ruleSetCreate, 121 Read: ruleSetRead, 122 Update: ruleSetUpdate, 123 Delete: ruleSetDelete, 124 Exists: ruleSetExists, 125 Importer: &schema.ResourceImporter{ 126 State: schema.ImportStatePassthrough, 127 }, 128 129 Schema: convertToHelperSchema(ruleSetDescriptions, map[schemaAttr]*schema.Schema{ 130 ruleSetCheckAttr: &schema.Schema{ 131 Type: schema.TypeString, 132 Required: true, 133 ValidateFunc: validateRegexp(ruleSetCheckAttr, config.CheckCIDRegex), 134 }, 135 ruleSetIfAttr: &schema.Schema{ 136 Type: schema.TypeList, 137 Required: true, 138 MinItems: 1, 139 Elem: &schema.Resource{ 140 Schema: convertToHelperSchema(ruleSetIfDescriptions, map[schemaAttr]*schema.Schema{ 141 ruleSetThenAttr: &schema.Schema{ 142 Type: schema.TypeSet, 143 MaxItems: 1, 144 Optional: true, 145 Elem: &schema.Resource{ 146 Schema: convertToHelperSchema(ruleSetIfThenDescriptions, map[schemaAttr]*schema.Schema{ 147 ruleSetAfterAttr: &schema.Schema{ 148 Type: schema.TypeString, 149 Optional: true, 150 DiffSuppressFunc: suppressEquivalentTimeDurations, 151 StateFunc: normalizeTimeDurationStringToSeconds, 152 ValidateFunc: validateFuncs( 153 validateDurationMin(ruleSetAfterAttr, "0s"), 154 ), 155 }, 156 ruleSetNotifyAttr: &schema.Schema{ 157 Type: schema.TypeList, 158 Optional: true, 159 MinItems: 1, 160 Elem: &schema.Schema{ 161 Type: schema.TypeString, 162 ValidateFunc: validateContactGroupCID(ruleSetNotifyAttr), 163 }, 164 }, 165 ruleSetSeverityAttr: &schema.Schema{ 166 Type: schema.TypeInt, 167 Optional: true, 168 Default: defaultAlertSeverity, 169 ValidateFunc: validateFuncs( 170 validateIntMax(ruleSetSeverityAttr, maxSeverity), 171 validateIntMin(ruleSetSeverityAttr, minSeverity), 172 ), 173 }, 174 }), 175 }, 176 }, 177 ruleSetValueAttr: &schema.Schema{ 178 Type: schema.TypeSet, 179 Optional: true, 180 MaxItems: 1, 181 Elem: &schema.Resource{ 182 Schema: convertToHelperSchema(ruleSetIfValueDescriptions, map[schemaAttr]*schema.Schema{ 183 ruleSetAbsentAttr: &schema.Schema{ 184 Type: schema.TypeString, // Applies to text or numeric metrics 185 Optional: true, 186 DiffSuppressFunc: suppressEquivalentTimeDurations, 187 StateFunc: normalizeTimeDurationStringToSeconds, 188 ValidateFunc: validateFuncs( 189 validateDurationMin(ruleSetAbsentAttr, ruleSetAbsentMin), 190 ), 191 ConflictsWith: makeConflictsWith(ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr, ruleSetOverAttr), 192 }, 193 ruleSetChangedAttr: &schema.Schema{ 194 Type: schema.TypeBool, // Applies to text or numeric metrics 195 Optional: true, 196 ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr, ruleSetOverAttr), 197 }, 198 ruleSetContainsAttr: &schema.Schema{ 199 Type: schema.TypeString, // Applies to text metrics only 200 Optional: true, 201 ValidateFunc: validateRegexp(ruleSetContainsAttr, `.+`), 202 ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr, ruleSetOverAttr), 203 }, 204 ruleSetMatchAttr: &schema.Schema{ 205 Type: schema.TypeString, // Applies to text metrics only 206 Optional: true, 207 ValidateFunc: validateRegexp(ruleSetMatchAttr, `.+`), 208 ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr, ruleSetOverAttr), 209 }, 210 ruleSetNotMatchAttr: &schema.Schema{ 211 Type: schema.TypeString, // Applies to text metrics only 212 Optional: true, 213 ValidateFunc: validateRegexp(ruleSetNotMatchAttr, `.+`), 214 ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr, ruleSetOverAttr), 215 }, 216 ruleSetMinValueAttr: &schema.Schema{ 217 Type: schema.TypeString, // Applies to numeric metrics only 218 Optional: true, 219 ValidateFunc: validateRegexp(ruleSetMinValueAttr, `.+`), // TODO(sean): improve this regexp to match int and float 220 ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr), 221 }, 222 ruleSetNotContainAttr: &schema.Schema{ 223 Type: schema.TypeString, // Applies to text metrics only 224 Optional: true, 225 ValidateFunc: validateRegexp(ruleSetNotContainAttr, `.+`), 226 ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetMaxValueAttr, ruleSetOverAttr), 227 }, 228 ruleSetMaxValueAttr: &schema.Schema{ 229 Type: schema.TypeString, // Applies to numeric metrics only 230 Optional: true, 231 ValidateFunc: validateRegexp(ruleSetMaxValueAttr, `.+`), // TODO(sean): improve this regexp to match int and float 232 ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr), 233 }, 234 ruleSetOverAttr: &schema.Schema{ 235 Type: schema.TypeSet, 236 Optional: true, 237 MaxItems: 1, 238 // ruleSetOverAttr is only compatible with checks of 239 // numeric type. NOTE: It may be premature to conflict with 240 // ruleSetChangedAttr. 241 ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetNotContainAttr), 242 Elem: &schema.Resource{ 243 Schema: convertToHelperSchema(ruleSetIfValueOverDescriptions, map[schemaAttr]*schema.Schema{ 244 ruleSetLastAttr: &schema.Schema{ 245 Type: schema.TypeString, 246 Optional: true, 247 Default: defaultRuleSetLast, 248 DiffSuppressFunc: suppressEquivalentTimeDurations, 249 StateFunc: normalizeTimeDurationStringToSeconds, 250 ValidateFunc: validateFuncs( 251 validateDurationMin(ruleSetLastAttr, "0s"), 252 ), 253 }, 254 ruleSetUsingAttr: &schema.Schema{ 255 Type: schema.TypeString, 256 Optional: true, 257 Default: defaultRuleSetWindowFunc, 258 ValidateFunc: validateStringIn(ruleSetUsingAttr, validRuleSetWindowFuncs), 259 }, 260 }), 261 }, 262 }, 263 }), 264 }, 265 }, 266 }), 267 }, 268 }, 269 ruleSetLinkAttr: &schema.Schema{ 270 Type: schema.TypeString, 271 Optional: true, 272 Computed: true, 273 ValidateFunc: validateHTTPURL(ruleSetLinkAttr, urlIsAbs|urlOptional), 274 }, 275 ruleSetMetricTypeAttr: &schema.Schema{ 276 Type: schema.TypeString, 277 Optional: true, 278 Default: defaultRuleSetMetricType, 279 ValidateFunc: validateStringIn(ruleSetMetricTypeAttr, validRuleSetMetricTypes), 280 }, 281 ruleSetNotesAttr: &schema.Schema{ 282 Type: schema.TypeString, 283 Optional: true, 284 Computed: true, 285 StateFunc: suppressWhitespace, 286 }, 287 ruleSetParentAttr: &schema.Schema{ 288 Type: schema.TypeString, 289 Optional: true, 290 Computed: true, 291 StateFunc: suppressWhitespace, 292 ValidateFunc: validateRegexp(ruleSetParentAttr, `^[\d]+_[\d\w]+$`), 293 }, 294 ruleSetMetricNameAttr: &schema.Schema{ 295 Type: schema.TypeString, 296 Required: true, 297 ValidateFunc: validateRegexp(ruleSetMetricNameAttr, `^[\S]+$`), 298 }, 299 ruleSetTagsAttr: tagMakeConfigSchema(ruleSetTagsAttr), 300 }), 301 } 302 } 303 304 func ruleSetCreate(d *schema.ResourceData, meta interface{}) error { 305 ctxt := meta.(*providerContext) 306 rs := newRuleSet() 307 308 if err := rs.ParseConfig(d); err != nil { 309 return errwrap.Wrapf("error parsing rule set schema during create: {{err}}", err) 310 } 311 312 if err := rs.Create(ctxt); err != nil { 313 return errwrap.Wrapf("error creating rule set: {{err}}", err) 314 } 315 316 d.SetId(rs.CID) 317 318 return ruleSetRead(d, meta) 319 } 320 321 func ruleSetExists(d *schema.ResourceData, meta interface{}) (bool, error) { 322 ctxt := meta.(*providerContext) 323 324 cid := d.Id() 325 rs, err := ctxt.client.FetchRuleSet(api.CIDType(&cid)) 326 if err != nil { 327 return false, err 328 } 329 330 if rs.CID == "" { 331 return false, nil 332 } 333 334 return true, nil 335 } 336 337 // ruleSetRead pulls data out of the RuleSet object and stores it into the 338 // appropriate place in the statefile. 339 func ruleSetRead(d *schema.ResourceData, meta interface{}) error { 340 ctxt := meta.(*providerContext) 341 342 cid := d.Id() 343 rs, err := loadRuleSet(ctxt, api.CIDType(&cid)) 344 if err != nil { 345 return err 346 } 347 348 d.SetId(rs.CID) 349 350 ifRules := make([]interface{}, 0, defaultRuleSetRuleLen) 351 for _, rule := range rs.Rules { 352 ifAttrs := make(map[string]interface{}, 2) 353 valueAttrs := make(map[string]interface{}, 2) 354 valueOverAttrs := make(map[string]interface{}, 2) 355 thenAttrs := make(map[string]interface{}, 3) 356 357 switch rule.Criteria { 358 case apiRuleSetAbsent: 359 d, _ := time.ParseDuration(fmt.Sprintf("%fs", rule.Value.(float64))) 360 valueAttrs[string(ruleSetAbsentAttr)] = fmt.Sprintf("%ds", int(d.Seconds())) 361 case apiRuleSetChanged: 362 valueAttrs[string(ruleSetChangedAttr)] = true 363 case apiRuleSetContains: 364 valueAttrs[string(ruleSetContainsAttr)] = rule.Value 365 case apiRuleSetMatch: 366 valueAttrs[string(ruleSetMatchAttr)] = rule.Value 367 case apiRuleSetMaxValue: 368 valueAttrs[string(ruleSetMaxValueAttr)] = rule.Value 369 case apiRuleSetMinValue: 370 valueAttrs[string(ruleSetMinValueAttr)] = rule.Value 371 case apiRuleSetNotContains: 372 valueAttrs[string(ruleSetNotContainAttr)] = rule.Value 373 case apiRuleSetNotMatch: 374 valueAttrs[string(ruleSetNotMatchAttr)] = rule.Value 375 default: 376 return fmt.Errorf("PROVIDER BUG: Unsupported criteria %q", rule.Criteria) 377 } 378 379 if rule.Wait > 0 { 380 thenAttrs[string(ruleSetAfterAttr)] = fmt.Sprintf("%ds", 60*rule.Wait) 381 } 382 thenAttrs[string(ruleSetSeverityAttr)] = int(rule.Severity) 383 384 if rule.WindowingFunction != nil { 385 valueOverAttrs[string(ruleSetUsingAttr)] = *rule.WindowingFunction 386 387 // NOTE: Only save the window duration if a function was specified 388 valueOverAttrs[string(ruleSetLastAttr)] = fmt.Sprintf("%ds", rule.WindowingDuration) 389 } 390 valueOverSet := schema.NewSet(ruleSetValueOverChecksum, nil) 391 valueOverSet.Add(valueOverAttrs) 392 valueAttrs[string(ruleSetOverAttr)] = valueOverSet 393 394 if contactGroups, ok := rs.ContactGroups[uint8(rule.Severity)]; ok { 395 sort.Strings(contactGroups) 396 thenAttrs[string(ruleSetNotifyAttr)] = contactGroups 397 } 398 thenSet := schema.NewSet(ruleSetThenChecksum, nil) 399 thenSet.Add(thenAttrs) 400 401 valueSet := schema.NewSet(ruleSetValueChecksum, nil) 402 valueSet.Add(valueAttrs) 403 ifAttrs[string(ruleSetThenAttr)] = thenSet 404 ifAttrs[string(ruleSetValueAttr)] = valueSet 405 406 ifRules = append(ifRules, ifAttrs) 407 } 408 409 d.Set(ruleSetCheckAttr, rs.CheckCID) 410 411 if err := d.Set(ruleSetIfAttr, ifRules); err != nil { 412 return errwrap.Wrapf(fmt.Sprintf("Unable to store rule set %q attribute: {{err}}", ruleSetIfAttr), err) 413 } 414 415 d.Set(ruleSetLinkAttr, indirect(rs.Link)) 416 d.Set(ruleSetMetricNameAttr, rs.MetricName) 417 d.Set(ruleSetMetricTypeAttr, rs.MetricType) 418 d.Set(ruleSetNotesAttr, indirect(rs.Notes)) 419 d.Set(ruleSetParentAttr, indirect(rs.Parent)) 420 421 if err := d.Set(ruleSetTagsAttr, tagsToState(apiToTags(rs.Tags))); err != nil { 422 return errwrap.Wrapf(fmt.Sprintf("Unable to store rule set %q attribute: {{err}}", ruleSetTagsAttr), err) 423 } 424 425 return nil 426 } 427 428 func ruleSetUpdate(d *schema.ResourceData, meta interface{}) error { 429 ctxt := meta.(*providerContext) 430 rs := newRuleSet() 431 432 if err := rs.ParseConfig(d); err != nil { 433 return err 434 } 435 436 rs.CID = d.Id() 437 if err := rs.Update(ctxt); err != nil { 438 return errwrap.Wrapf(fmt.Sprintf("unable to update rule set %q: {{err}}", d.Id()), err) 439 } 440 441 return ruleSetRead(d, meta) 442 } 443 444 func ruleSetDelete(d *schema.ResourceData, meta interface{}) error { 445 ctxt := meta.(*providerContext) 446 447 cid := d.Id() 448 if _, err := ctxt.client.DeleteRuleSetByCID(api.CIDType(&cid)); err != nil { 449 return errwrap.Wrapf(fmt.Sprintf("unable to delete rule set %q: {{err}}", d.Id()), err) 450 } 451 452 d.SetId("") 453 454 return nil 455 } 456 457 type circonusRuleSet struct { 458 api.RuleSet 459 } 460 461 func newRuleSet() circonusRuleSet { 462 rs := circonusRuleSet{ 463 RuleSet: *api.NewRuleSet(), 464 } 465 466 rs.ContactGroups = make(map[uint8][]string, config.NumSeverityLevels) 467 for i := uint8(0); i < config.NumSeverityLevels; i++ { 468 rs.ContactGroups[i+1] = make([]string, 0, 1) 469 } 470 471 rs.Rules = make([]api.RuleSetRule, 0, 1) 472 473 return rs 474 } 475 476 func loadRuleSet(ctxt *providerContext, cid api.CIDType) (circonusRuleSet, error) { 477 var rs circonusRuleSet 478 crs, err := ctxt.client.FetchRuleSet(cid) 479 if err != nil { 480 return circonusRuleSet{}, err 481 } 482 rs.RuleSet = *crs 483 484 return rs, nil 485 } 486 487 func ruleSetThenChecksum(v interface{}) int { 488 b := &bytes.Buffer{} 489 b.Grow(defaultHashBufSize) 490 491 writeInt := func(m map[string]interface{}, attrName string) { 492 if v, found := m[attrName]; found { 493 i := v.(int) 494 if i != 0 { 495 fmt.Fprintf(b, "%x", i) 496 } 497 } 498 } 499 500 writeString := func(m map[string]interface{}, attrName string) { 501 if v, found := m[attrName]; found { 502 s := strings.TrimSpace(v.(string)) 503 if s != "" { 504 fmt.Fprint(b, s) 505 } 506 } 507 } 508 509 writeStringArray := func(m map[string]interface{}, attrName string) { 510 if v, found := m[attrName]; found { 511 a := v.([]string) 512 if a != nil { 513 sort.Strings(a) 514 for _, s := range a { 515 fmt.Fprint(b, strings.TrimSpace(s)) 516 } 517 } 518 } 519 } 520 521 m := v.(map[string]interface{}) 522 523 writeString(m, ruleSetAfterAttr) 524 writeStringArray(m, ruleSetNotifyAttr) 525 writeInt(m, ruleSetSeverityAttr) 526 527 s := b.String() 528 return hashcode.String(s) 529 } 530 531 func ruleSetValueChecksum(v interface{}) int { 532 b := &bytes.Buffer{} 533 b.Grow(defaultHashBufSize) 534 535 writeBool := func(m map[string]interface{}, attrName string) { 536 if v, found := m[attrName]; found { 537 fmt.Fprintf(b, "%t", v.(bool)) 538 } 539 } 540 541 writeDuration := func(m map[string]interface{}, attrName string) { 542 if v, found := m[attrName]; found { 543 s := v.(string) 544 if s != "" { 545 d, _ := time.ParseDuration(s) 546 fmt.Fprint(b, d.String()) 547 } 548 } 549 } 550 551 writeString := func(m map[string]interface{}, attrName string) { 552 if v, found := m[attrName]; found { 553 s := strings.TrimSpace(v.(string)) 554 if s != "" { 555 fmt.Fprint(b, s) 556 } 557 } 558 } 559 560 m := v.(map[string]interface{}) 561 562 if v, found := m[ruleSetValueAttr]; found { 563 valueMap := v.(map[string]interface{}) 564 if valueMap != nil { 565 writeDuration(valueMap, ruleSetAbsentAttr) 566 writeBool(valueMap, ruleSetChangedAttr) 567 writeString(valueMap, ruleSetContainsAttr) 568 writeString(valueMap, ruleSetMatchAttr) 569 writeString(valueMap, ruleSetNotMatchAttr) 570 writeString(valueMap, ruleSetMinValueAttr) 571 writeString(valueMap, ruleSetNotContainAttr) 572 writeString(valueMap, ruleSetMaxValueAttr) 573 574 if v, found := valueMap[ruleSetOverAttr]; found { 575 overMap := v.(map[string]interface{}) 576 writeDuration(overMap, ruleSetLastAttr) 577 writeString(overMap, ruleSetUsingAttr) 578 } 579 } 580 } 581 582 s := b.String() 583 return hashcode.String(s) 584 } 585 586 func ruleSetValueOverChecksum(v interface{}) int { 587 b := &bytes.Buffer{} 588 b.Grow(defaultHashBufSize) 589 590 writeString := func(m map[string]interface{}, attrName string) { 591 if v, found := m[attrName]; found { 592 s := strings.TrimSpace(v.(string)) 593 if s != "" { 594 fmt.Fprint(b, s) 595 } 596 } 597 } 598 599 m := v.(map[string]interface{}) 600 601 writeString(m, ruleSetLastAttr) 602 writeString(m, ruleSetUsingAttr) 603 604 s := b.String() 605 return hashcode.String(s) 606 } 607 608 // ParseConfig reads Terraform config data and stores the information into a 609 // Circonus RuleSet object. ParseConfig, ruleSetRead(), and ruleSetChecksum 610 // must be kept in sync. 611 func (rs *circonusRuleSet) ParseConfig(d *schema.ResourceData) error { 612 if v, found := d.GetOk(ruleSetCheckAttr); found { 613 rs.CheckCID = v.(string) 614 } 615 616 if v, found := d.GetOk(ruleSetLinkAttr); found { 617 s := v.(string) 618 rs.Link = &s 619 } 620 621 if v, found := d.GetOk(ruleSetMetricTypeAttr); found { 622 rs.MetricType = v.(string) 623 } 624 625 if v, found := d.GetOk(ruleSetNotesAttr); found { 626 s := v.(string) 627 rs.Notes = &s 628 } 629 630 if v, found := d.GetOk(ruleSetParentAttr); found { 631 s := v.(string) 632 rs.Parent = &s 633 } 634 635 if v, found := d.GetOk(ruleSetMetricNameAttr); found { 636 rs.MetricName = v.(string) 637 } 638 639 rs.Rules = make([]api.RuleSetRule, 0, defaultRuleSetRuleLen) 640 if ifListRaw, found := d.GetOk(ruleSetIfAttr); found { 641 ifList := ifListRaw.([]interface{}) 642 for _, ifListElem := range ifList { 643 ifAttrs := newInterfaceMap(ifListElem.(map[string]interface{})) 644 645 rule := api.RuleSetRule{} 646 647 if thenListRaw, found := ifAttrs[ruleSetThenAttr]; found { 648 thenList := thenListRaw.(*schema.Set).List() 649 650 for _, thenListRaw := range thenList { 651 thenAttrs := newInterfaceMap(thenListRaw) 652 653 if v, found := thenAttrs[ruleSetAfterAttr]; found { 654 s := v.(string) 655 if s != "" { 656 d, err := time.ParseDuration(v.(string)) 657 if err != nil { 658 return errwrap.Wrapf(fmt.Sprintf("unable to parse %q duration %q: {{err}}", ruleSetAfterAttr, v.(string)), err) 659 } 660 rule.Wait = uint(d.Minutes()) 661 } 662 } 663 664 // NOTE: break from convention of alpha sorting attributes and handle Notify after Severity 665 666 if i, found := thenAttrs[ruleSetSeverityAttr]; found { 667 rule.Severity = uint(i.(int)) 668 } 669 670 if notifyListRaw, found := thenAttrs[ruleSetNotifyAttr]; found { 671 notifyList := interfaceList(notifyListRaw.([]interface{})) 672 673 sev := uint8(rule.Severity) 674 for _, contactGroupCID := range notifyList.List() { 675 var found bool 676 if contactGroups, ok := rs.ContactGroups[sev]; ok { 677 for _, contactGroup := range contactGroups { 678 if contactGroup == contactGroupCID { 679 found = true 680 break 681 } 682 } 683 } 684 if !found { 685 rs.ContactGroups[sev] = append(rs.ContactGroups[sev], contactGroupCID) 686 } 687 } 688 } 689 } 690 } 691 692 if ruleSetValueListRaw, found := ifAttrs[ruleSetValueAttr]; found { 693 ruleSetValueList := ruleSetValueListRaw.(*schema.Set).List() 694 695 for _, valueListRaw := range ruleSetValueList { 696 valueAttrs := newInterfaceMap(valueListRaw) 697 698 METRIC_TYPE: 699 switch rs.MetricType { 700 case ruleSetMetricTypeNumeric: 701 if v, found := valueAttrs[ruleSetAbsentAttr]; found { 702 s := v.(string) 703 if s != "" { 704 d, _ := time.ParseDuration(s) 705 rule.Criteria = apiRuleSetAbsent 706 rule.Value = float64(d.Seconds()) 707 break METRIC_TYPE 708 } 709 } 710 711 if v, found := valueAttrs[ruleSetChangedAttr]; found { 712 b := v.(bool) 713 if b { 714 rule.Criteria = apiRuleSetChanged 715 break METRIC_TYPE 716 } 717 } 718 719 if v, found := valueAttrs[ruleSetMinValueAttr]; found { 720 s := v.(string) 721 if s != "" { 722 rule.Criteria = apiRuleSetMinValue 723 rule.Value = s 724 break METRIC_TYPE 725 } 726 } 727 728 if v, found := valueAttrs[ruleSetMaxValueAttr]; found { 729 s := v.(string) 730 if s != "" { 731 rule.Criteria = apiRuleSetMaxValue 732 rule.Value = s 733 break METRIC_TYPE 734 } 735 } 736 case ruleSetMetricTypeText: 737 if v, found := valueAttrs[ruleSetAbsentAttr]; found { 738 s := v.(string) 739 if s != "" { 740 d, _ := time.ParseDuration(s) 741 rule.Criteria = apiRuleSetAbsent 742 rule.Value = float64(d.Seconds()) 743 break METRIC_TYPE 744 } 745 } 746 747 if v, found := valueAttrs[ruleSetChangedAttr]; found { 748 b := v.(bool) 749 if b { 750 rule.Criteria = apiRuleSetChanged 751 break METRIC_TYPE 752 } 753 } 754 755 if v, found := valueAttrs[ruleSetContainsAttr]; found { 756 s := v.(string) 757 if s != "" { 758 rule.Criteria = apiRuleSetContains 759 rule.Value = s 760 break METRIC_TYPE 761 } 762 } 763 764 if v, found := valueAttrs[ruleSetMatchAttr]; found { 765 s := v.(string) 766 if s != "" { 767 rule.Criteria = apiRuleSetMatch 768 rule.Value = s 769 break METRIC_TYPE 770 } 771 } 772 773 if v, found := valueAttrs[ruleSetNotMatchAttr]; found { 774 s := v.(string) 775 if s != "" { 776 rule.Criteria = apiRuleSetNotMatch 777 rule.Value = s 778 break METRIC_TYPE 779 } 780 } 781 782 if v, found := valueAttrs[ruleSetNotContainAttr]; found { 783 s := v.(string) 784 if s != "" { 785 rule.Criteria = apiRuleSetNotContains 786 rule.Value = s 787 break METRIC_TYPE 788 } 789 } 790 default: 791 return fmt.Errorf("PROVIDER BUG: unsupported rule set metric type: %q", rs.MetricType) 792 } 793 794 if ruleSetOverListRaw, found := valueAttrs[ruleSetOverAttr]; found { 795 overList := ruleSetOverListRaw.(*schema.Set).List() 796 797 for _, overListRaw := range overList { 798 overAttrs := newInterfaceMap(overListRaw) 799 800 if v, found := overAttrs[ruleSetLastAttr]; found { 801 last, err := time.ParseDuration(v.(string)) 802 if err != nil { 803 return errwrap.Wrapf(fmt.Sprintf("unable to parse duration %s attribute", ruleSetLastAttr), err) 804 } 805 rule.WindowingDuration = uint(last.Seconds()) 806 } 807 808 if v, found := overAttrs[ruleSetUsingAttr]; found { 809 s := v.(string) 810 rule.WindowingFunction = &s 811 } 812 } 813 } 814 } 815 } 816 rs.Rules = append(rs.Rules, rule) 817 } 818 } 819 820 if v, found := d.GetOk(ruleSetTagsAttr); found { 821 rs.Tags = derefStringList(flattenSet(v.(*schema.Set))) 822 } 823 824 if err := rs.Validate(); err != nil { 825 return err 826 } 827 828 return nil 829 } 830 831 func (rs *circonusRuleSet) Create(ctxt *providerContext) error { 832 crs, err := ctxt.client.CreateRuleSet(&rs.RuleSet) 833 if err != nil { 834 return err 835 } 836 837 rs.CID = crs.CID 838 839 return nil 840 } 841 842 func (rs *circonusRuleSet) Update(ctxt *providerContext) error { 843 _, err := ctxt.client.UpdateRuleSet(&rs.RuleSet) 844 if err != nil { 845 return errwrap.Wrapf(fmt.Sprintf("Unable to update rule set %s: {{err}}", rs.CID), err) 846 } 847 848 return nil 849 } 850 851 func (rs *circonusRuleSet) Validate() error { 852 // TODO(sean@): From https://login.circonus.com/resources/api/calls/rule_set 853 // under `value`: 854 // 855 // For an 'on absence' rule this is the number of seconds the metric must not 856 // have been collected for, and should not be lower than either the period or 857 // timeout of the metric being collected. 858 859 for i, rule := range rs.Rules { 860 if rule.Criteria == "" { 861 return fmt.Errorf("rule %d for check ID %s has an empty criteria", i, rs.CheckCID) 862 } 863 } 864 865 return nil 866 }