867 lines
28 KiB
Go
867 lines
28 KiB
Go
package circonus
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/circonus-labs/circonus-gometrics/api"
|
|
"github.com/circonus-labs/circonus-gometrics/api/config"
|
|
"github.com/hashicorp/errwrap"
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
)
|
|
|
|
const (
|
|
// circonus_rule_set.* resource attribute names
|
|
ruleSetCheckAttr = "check"
|
|
ruleSetIfAttr = "if"
|
|
ruleSetLinkAttr = "link"
|
|
ruleSetMetricTypeAttr = "metric_type"
|
|
ruleSetNotesAttr = "notes"
|
|
ruleSetParentAttr = "parent"
|
|
ruleSetMetricNameAttr = "metric_name"
|
|
ruleSetTagsAttr = "tags"
|
|
|
|
// circonus_rule_set.if.* resource attribute names
|
|
ruleSetThenAttr = "then"
|
|
ruleSetValueAttr = "value"
|
|
|
|
// circonus_rule_set.if.then.* resource attribute names
|
|
ruleSetAfterAttr = "after"
|
|
ruleSetNotifyAttr = "notify"
|
|
ruleSetSeverityAttr = "severity"
|
|
|
|
// circonus_rule_set.if.value.* resource attribute names
|
|
ruleSetAbsentAttr = "absent" // apiRuleSetAbsent
|
|
ruleSetChangedAttr = "changed" // apiRuleSetChanged
|
|
ruleSetContainsAttr = "contains" // apiRuleSetContains
|
|
ruleSetMatchAttr = "match" // apiRuleSetMatch
|
|
ruleSetMaxValueAttr = "max_value" // apiRuleSetMaxValue
|
|
ruleSetMinValueAttr = "min_value" // apiRuleSetMinValue
|
|
ruleSetNotContainAttr = "not_contain" // apiRuleSetNotContains
|
|
ruleSetNotMatchAttr = "not_match" // apiRuleSetNotMatch
|
|
ruleSetOverAttr = "over"
|
|
|
|
// circonus_rule_set.if.value.over.* resource attribute names
|
|
ruleSetLastAttr = "last"
|
|
ruleSetUsingAttr = "using"
|
|
)
|
|
|
|
const (
|
|
// Different criteria that an api.RuleSetRule can return
|
|
apiRuleSetAbsent = "on absence" // ruleSetAbsentAttr
|
|
apiRuleSetChanged = "on change" // ruleSetChangedAttr
|
|
apiRuleSetContains = "contains" // ruleSetContainsAttr
|
|
apiRuleSetMatch = "match" // ruleSetMatchAttr
|
|
apiRuleSetMaxValue = "max value" // ruleSetMaxValueAttr
|
|
apiRuleSetMinValue = "min value" // ruleSetMinValueAttr
|
|
apiRuleSetNotContains = "does not contain" // ruleSetNotContainAttr
|
|
apiRuleSetNotMatch = "does not match" // ruleSetNotMatchAttr
|
|
)
|
|
|
|
var ruleSetDescriptions = attrDescrs{
|
|
// circonus_rule_set.* resource attribute names
|
|
ruleSetCheckAttr: "The CID of the check that contains the metric for this rule set",
|
|
ruleSetIfAttr: "A rule to execute for this rule set",
|
|
ruleSetLinkAttr: "URL to show users when this rule set is active (e.g. wiki)",
|
|
ruleSetMetricTypeAttr: "The type of data flowing through the specified metric stream",
|
|
ruleSetNotesAttr: "Notes describing this rule set",
|
|
ruleSetParentAttr: "Parent CID that must be healthy for this rule set to be active",
|
|
ruleSetMetricNameAttr: "The name of the metric stream within a check to register the rule set with",
|
|
ruleSetTagsAttr: "Tags associated with this rule set",
|
|
}
|
|
|
|
var ruleSetIfDescriptions = attrDescrs{
|
|
// circonus_rule_set.if.* resource attribute names
|
|
ruleSetThenAttr: "Description of the action(s) to take when this rule set is active",
|
|
ruleSetValueAttr: "Predicate that the rule set uses to evaluate a stream of metrics",
|
|
}
|
|
|
|
var ruleSetIfValueDescriptions = attrDescrs{
|
|
// circonus_rule_set.if.value.* resource attribute names
|
|
ruleSetAbsentAttr: "Fire the rule set if there has been no data for the given metric stream over the last duration",
|
|
ruleSetChangedAttr: "Boolean indicating the value has changed",
|
|
ruleSetContainsAttr: "Fire the rule set if the text metric contain the following string",
|
|
ruleSetMatchAttr: "Fire the rule set if the text metric exactly match the following string",
|
|
ruleSetNotMatchAttr: "Fire the rule set if the text metric not match the following string",
|
|
ruleSetMinValueAttr: "Fire the rule set if the numeric value less than the specified value",
|
|
ruleSetNotContainAttr: "Fire the rule set if the text metric does not contain the following string",
|
|
ruleSetMaxValueAttr: "Fire the rule set if the numeric value is more than the specified value",
|
|
ruleSetOverAttr: "Use a derived value using a window",
|
|
ruleSetThenAttr: "Action to take when the rule set is active",
|
|
}
|
|
|
|
var ruleSetIfValueOverDescriptions = attrDescrs{
|
|
// circonus_rule_set.if.value.over.* resource attribute names
|
|
ruleSetLastAttr: "Duration over which data from the last interval is examined",
|
|
ruleSetUsingAttr: "Define the window funciton to use over the last duration",
|
|
}
|
|
|
|
var ruleSetIfThenDescriptions = attrDescrs{
|
|
// circonus_rule_set.if.then.* resource attribute names
|
|
ruleSetAfterAttr: "The length of time we should wait before contacting the contact groups after this ruleset has faulted.",
|
|
ruleSetNotifyAttr: "List of contact groups to notify at the following appropriate severity if this rule set is active.",
|
|
ruleSetSeverityAttr: "Send a notification at this severity level.",
|
|
}
|
|
|
|
func resourceRuleSet() *schema.Resource {
|
|
makeConflictsWith := func(in ...schemaAttr) []string {
|
|
out := make([]string, 0, len(in))
|
|
for _, attr := range in {
|
|
out = append(out, string(ruleSetIfAttr)+"."+string(ruleSetValueAttr)+"."+string(attr))
|
|
}
|
|
return out
|
|
}
|
|
|
|
return &schema.Resource{
|
|
Create: ruleSetCreate,
|
|
Read: ruleSetRead,
|
|
Update: ruleSetUpdate,
|
|
Delete: ruleSetDelete,
|
|
Exists: ruleSetExists,
|
|
Importer: &schema.ResourceImporter{
|
|
State: schema.ImportStatePassthrough,
|
|
},
|
|
|
|
Schema: convertToHelperSchema(ruleSetDescriptions, map[schemaAttr]*schema.Schema{
|
|
ruleSetCheckAttr: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ValidateFunc: validateRegexp(ruleSetCheckAttr, config.CheckCIDRegex),
|
|
},
|
|
ruleSetIfAttr: &schema.Schema{
|
|
Type: schema.TypeList,
|
|
Required: true,
|
|
MinItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: convertToHelperSchema(ruleSetIfDescriptions, map[schemaAttr]*schema.Schema{
|
|
ruleSetThenAttr: &schema.Schema{
|
|
Type: schema.TypeSet,
|
|
MaxItems: 1,
|
|
Optional: true,
|
|
Elem: &schema.Resource{
|
|
Schema: convertToHelperSchema(ruleSetIfThenDescriptions, map[schemaAttr]*schema.Schema{
|
|
ruleSetAfterAttr: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
DiffSuppressFunc: suppressEquivalentTimeDurations,
|
|
StateFunc: normalizeTimeDurationStringToSeconds,
|
|
ValidateFunc: validateFuncs(
|
|
validateDurationMin(ruleSetAfterAttr, "0s"),
|
|
),
|
|
},
|
|
ruleSetNotifyAttr: &schema.Schema{
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
MinItems: 1,
|
|
Elem: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
ValidateFunc: validateContactGroupCID(ruleSetNotifyAttr),
|
|
},
|
|
},
|
|
ruleSetSeverityAttr: &schema.Schema{
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
Default: defaultAlertSeverity,
|
|
ValidateFunc: validateFuncs(
|
|
validateIntMax(ruleSetSeverityAttr, maxSeverity),
|
|
validateIntMin(ruleSetSeverityAttr, minSeverity),
|
|
),
|
|
},
|
|
}),
|
|
},
|
|
},
|
|
ruleSetValueAttr: &schema.Schema{
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: convertToHelperSchema(ruleSetIfValueDescriptions, map[schemaAttr]*schema.Schema{
|
|
ruleSetAbsentAttr: &schema.Schema{
|
|
Type: schema.TypeString, // Applies to text or numeric metrics
|
|
Optional: true,
|
|
DiffSuppressFunc: suppressEquivalentTimeDurations,
|
|
StateFunc: normalizeTimeDurationStringToSeconds,
|
|
ValidateFunc: validateFuncs(
|
|
validateDurationMin(ruleSetAbsentAttr, ruleSetAbsentMin),
|
|
),
|
|
ConflictsWith: makeConflictsWith(ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr, ruleSetOverAttr),
|
|
},
|
|
ruleSetChangedAttr: &schema.Schema{
|
|
Type: schema.TypeBool, // Applies to text or numeric metrics
|
|
Optional: true,
|
|
ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr, ruleSetOverAttr),
|
|
},
|
|
ruleSetContainsAttr: &schema.Schema{
|
|
Type: schema.TypeString, // Applies to text metrics only
|
|
Optional: true,
|
|
ValidateFunc: validateRegexp(ruleSetContainsAttr, `.+`),
|
|
ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr, ruleSetOverAttr),
|
|
},
|
|
ruleSetMatchAttr: &schema.Schema{
|
|
Type: schema.TypeString, // Applies to text metrics only
|
|
Optional: true,
|
|
ValidateFunc: validateRegexp(ruleSetMatchAttr, `.+`),
|
|
ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr, ruleSetOverAttr),
|
|
},
|
|
ruleSetNotMatchAttr: &schema.Schema{
|
|
Type: schema.TypeString, // Applies to text metrics only
|
|
Optional: true,
|
|
ValidateFunc: validateRegexp(ruleSetNotMatchAttr, `.+`),
|
|
ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr, ruleSetOverAttr),
|
|
},
|
|
ruleSetMinValueAttr: &schema.Schema{
|
|
Type: schema.TypeString, // Applies to numeric metrics only
|
|
Optional: true,
|
|
ValidateFunc: validateRegexp(ruleSetMinValueAttr, `.+`), // TODO(sean): improve this regexp to match int and float
|
|
ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetNotContainAttr, ruleSetMaxValueAttr),
|
|
},
|
|
ruleSetNotContainAttr: &schema.Schema{
|
|
Type: schema.TypeString, // Applies to text metrics only
|
|
Optional: true,
|
|
ValidateFunc: validateRegexp(ruleSetNotContainAttr, `.+`),
|
|
ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetMaxValueAttr, ruleSetOverAttr),
|
|
},
|
|
ruleSetMaxValueAttr: &schema.Schema{
|
|
Type: schema.TypeString, // Applies to numeric metrics only
|
|
Optional: true,
|
|
ValidateFunc: validateRegexp(ruleSetMaxValueAttr, `.+`), // TODO(sean): improve this regexp to match int and float
|
|
ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetMinValueAttr, ruleSetNotContainAttr),
|
|
},
|
|
ruleSetOverAttr: &schema.Schema{
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
MaxItems: 1,
|
|
// ruleSetOverAttr is only compatible with checks of
|
|
// numeric type. NOTE: It may be premature to conflict with
|
|
// ruleSetChangedAttr.
|
|
ConflictsWith: makeConflictsWith(ruleSetAbsentAttr, ruleSetChangedAttr, ruleSetContainsAttr, ruleSetMatchAttr, ruleSetNotMatchAttr, ruleSetNotContainAttr),
|
|
Elem: &schema.Resource{
|
|
Schema: convertToHelperSchema(ruleSetIfValueOverDescriptions, map[schemaAttr]*schema.Schema{
|
|
ruleSetLastAttr: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Default: defaultRuleSetLast,
|
|
DiffSuppressFunc: suppressEquivalentTimeDurations,
|
|
StateFunc: normalizeTimeDurationStringToSeconds,
|
|
ValidateFunc: validateFuncs(
|
|
validateDurationMin(ruleSetLastAttr, "0s"),
|
|
),
|
|
},
|
|
ruleSetUsingAttr: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Default: defaultRuleSetWindowFunc,
|
|
ValidateFunc: validateStringIn(ruleSetUsingAttr, validRuleSetWindowFuncs),
|
|
},
|
|
}),
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
},
|
|
ruleSetLinkAttr: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Computed: true,
|
|
ValidateFunc: validateHTTPURL(ruleSetLinkAttr, urlIsAbs|urlOptional),
|
|
},
|
|
ruleSetMetricTypeAttr: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Default: defaultRuleSetMetricType,
|
|
ValidateFunc: validateStringIn(ruleSetMetricTypeAttr, validRuleSetMetricTypes),
|
|
},
|
|
ruleSetNotesAttr: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Computed: true,
|
|
StateFunc: suppressWhitespace,
|
|
},
|
|
ruleSetParentAttr: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Computed: true,
|
|
StateFunc: suppressWhitespace,
|
|
ValidateFunc: validateRegexp(ruleSetParentAttr, `^[\d]+_[\d\w]+$`),
|
|
},
|
|
ruleSetMetricNameAttr: &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ValidateFunc: validateRegexp(ruleSetMetricNameAttr, `^[\S]+$`),
|
|
},
|
|
ruleSetTagsAttr: tagMakeConfigSchema(ruleSetTagsAttr),
|
|
}),
|
|
}
|
|
}
|
|
|
|
func ruleSetCreate(d *schema.ResourceData, meta interface{}) error {
|
|
ctxt := meta.(*providerContext)
|
|
rs := newRuleSet()
|
|
|
|
if err := rs.ParseConfig(d); err != nil {
|
|
return errwrap.Wrapf("error parsing rule set schema during create: {{err}}", err)
|
|
}
|
|
|
|
if err := rs.Create(ctxt); err != nil {
|
|
return errwrap.Wrapf("error creating rule set: {{err}}", err)
|
|
}
|
|
|
|
d.SetId(rs.CID)
|
|
|
|
return ruleSetRead(d, meta)
|
|
}
|
|
|
|
func ruleSetExists(d *schema.ResourceData, meta interface{}) (bool, error) {
|
|
ctxt := meta.(*providerContext)
|
|
|
|
cid := d.Id()
|
|
rs, err := ctxt.client.FetchRuleSet(api.CIDType(&cid))
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if rs.CID == "" {
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// ruleSetRead pulls data out of the RuleSet object and stores it into the
|
|
// appropriate place in the statefile.
|
|
func ruleSetRead(d *schema.ResourceData, meta interface{}) error {
|
|
ctxt := meta.(*providerContext)
|
|
|
|
cid := d.Id()
|
|
rs, err := loadRuleSet(ctxt, api.CIDType(&cid))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.SetId(rs.CID)
|
|
|
|
ifRules := make([]interface{}, 0, defaultRuleSetRuleLen)
|
|
for _, rule := range rs.Rules {
|
|
ifAttrs := make(map[string]interface{}, 2)
|
|
valueAttrs := make(map[string]interface{}, 2)
|
|
valueOverAttrs := make(map[string]interface{}, 2)
|
|
thenAttrs := make(map[string]interface{}, 3)
|
|
|
|
switch rule.Criteria {
|
|
case apiRuleSetAbsent:
|
|
d, _ := time.ParseDuration(fmt.Sprintf("%fs", rule.Value.(float64)))
|
|
valueAttrs[string(ruleSetAbsentAttr)] = fmt.Sprintf("%ds", int(d.Seconds()))
|
|
case apiRuleSetChanged:
|
|
valueAttrs[string(ruleSetChangedAttr)] = true
|
|
case apiRuleSetContains:
|
|
valueAttrs[string(ruleSetContainsAttr)] = rule.Value
|
|
case apiRuleSetMatch:
|
|
valueAttrs[string(ruleSetMatchAttr)] = rule.Value
|
|
case apiRuleSetMaxValue:
|
|
valueAttrs[string(ruleSetMaxValueAttr)] = rule.Value
|
|
case apiRuleSetMinValue:
|
|
valueAttrs[string(ruleSetMinValueAttr)] = rule.Value
|
|
case apiRuleSetNotContains:
|
|
valueAttrs[string(ruleSetNotContainAttr)] = rule.Value
|
|
case apiRuleSetNotMatch:
|
|
valueAttrs[string(ruleSetNotMatchAttr)] = rule.Value
|
|
default:
|
|
return fmt.Errorf("PROVIDER BUG: Unsupported criteria %q", rule.Criteria)
|
|
}
|
|
|
|
if rule.Wait > 0 {
|
|
thenAttrs[string(ruleSetAfterAttr)] = fmt.Sprintf("%ds", 60*rule.Wait)
|
|
}
|
|
thenAttrs[string(ruleSetSeverityAttr)] = int(rule.Severity)
|
|
|
|
if rule.WindowingFunction != nil {
|
|
valueOverAttrs[string(ruleSetUsingAttr)] = *rule.WindowingFunction
|
|
|
|
// NOTE: Only save the window duration if a function was specified
|
|
valueOverAttrs[string(ruleSetLastAttr)] = fmt.Sprintf("%ds", rule.WindowingDuration)
|
|
}
|
|
valueOverSet := schema.NewSet(ruleSetValueOverChecksum, nil)
|
|
valueOverSet.Add(valueOverAttrs)
|
|
valueAttrs[string(ruleSetOverAttr)] = valueOverSet
|
|
|
|
if contactGroups, ok := rs.ContactGroups[uint8(rule.Severity)]; ok {
|
|
sort.Strings(contactGroups)
|
|
thenAttrs[string(ruleSetNotifyAttr)] = contactGroups
|
|
}
|
|
thenSet := schema.NewSet(ruleSetThenChecksum, nil)
|
|
thenSet.Add(thenAttrs)
|
|
|
|
valueSet := schema.NewSet(ruleSetValueChecksum, nil)
|
|
valueSet.Add(valueAttrs)
|
|
ifAttrs[string(ruleSetThenAttr)] = thenSet
|
|
ifAttrs[string(ruleSetValueAttr)] = valueSet
|
|
|
|
ifRules = append(ifRules, ifAttrs)
|
|
}
|
|
|
|
d.Set(ruleSetCheckAttr, rs.CheckCID)
|
|
|
|
if err := d.Set(ruleSetIfAttr, ifRules); err != nil {
|
|
return errwrap.Wrapf(fmt.Sprintf("Unable to store rule set %q attribute: {{err}}", ruleSetIfAttr), err)
|
|
}
|
|
|
|
d.Set(ruleSetLinkAttr, indirect(rs.Link))
|
|
d.Set(ruleSetMetricNameAttr, rs.MetricName)
|
|
d.Set(ruleSetMetricTypeAttr, rs.MetricType)
|
|
d.Set(ruleSetNotesAttr, indirect(rs.Notes))
|
|
d.Set(ruleSetParentAttr, indirect(rs.Parent))
|
|
|
|
if err := d.Set(ruleSetTagsAttr, tagsToState(apiToTags(rs.Tags))); err != nil {
|
|
return errwrap.Wrapf(fmt.Sprintf("Unable to store rule set %q attribute: {{err}}", ruleSetTagsAttr), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func ruleSetUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
ctxt := meta.(*providerContext)
|
|
rs := newRuleSet()
|
|
|
|
if err := rs.ParseConfig(d); err != nil {
|
|
return err
|
|
}
|
|
|
|
rs.CID = d.Id()
|
|
if err := rs.Update(ctxt); err != nil {
|
|
return errwrap.Wrapf(fmt.Sprintf("unable to update rule set %q: {{err}}", d.Id()), err)
|
|
}
|
|
|
|
return ruleSetRead(d, meta)
|
|
}
|
|
|
|
func ruleSetDelete(d *schema.ResourceData, meta interface{}) error {
|
|
ctxt := meta.(*providerContext)
|
|
|
|
cid := d.Id()
|
|
if _, err := ctxt.client.DeleteRuleSetByCID(api.CIDType(&cid)); err != nil {
|
|
return errwrap.Wrapf(fmt.Sprintf("unable to delete rule set %q: {{err}}", d.Id()), err)
|
|
}
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
}
|
|
|
|
type circonusRuleSet struct {
|
|
api.RuleSet
|
|
}
|
|
|
|
func newRuleSet() circonusRuleSet {
|
|
rs := circonusRuleSet{
|
|
RuleSet: *api.NewRuleSet(),
|
|
}
|
|
|
|
rs.ContactGroups = make(map[uint8][]string, config.NumSeverityLevels)
|
|
for i := uint8(0); i < config.NumSeverityLevels; i++ {
|
|
rs.ContactGroups[i+1] = make([]string, 0, 1)
|
|
}
|
|
|
|
rs.Rules = make([]api.RuleSetRule, 0, 1)
|
|
|
|
return rs
|
|
}
|
|
|
|
func loadRuleSet(ctxt *providerContext, cid api.CIDType) (circonusRuleSet, error) {
|
|
var rs circonusRuleSet
|
|
crs, err := ctxt.client.FetchRuleSet(cid)
|
|
if err != nil {
|
|
return circonusRuleSet{}, err
|
|
}
|
|
rs.RuleSet = *crs
|
|
|
|
return rs, nil
|
|
}
|
|
|
|
func ruleSetThenChecksum(v interface{}) int {
|
|
b := &bytes.Buffer{}
|
|
b.Grow(defaultHashBufSize)
|
|
|
|
writeInt := func(m map[string]interface{}, attrName string) {
|
|
if v, found := m[attrName]; found {
|
|
i := v.(int)
|
|
if i != 0 {
|
|
fmt.Fprintf(b, "%x", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
writeString := func(m map[string]interface{}, attrName string) {
|
|
if v, found := m[attrName]; found {
|
|
s := strings.TrimSpace(v.(string))
|
|
if s != "" {
|
|
fmt.Fprint(b, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
writeStringArray := func(m map[string]interface{}, attrName string) {
|
|
if v, found := m[attrName]; found {
|
|
a := v.([]string)
|
|
if a != nil {
|
|
sort.Strings(a)
|
|
for _, s := range a {
|
|
fmt.Fprint(b, strings.TrimSpace(s))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
writeString(m, ruleSetAfterAttr)
|
|
writeStringArray(m, ruleSetNotifyAttr)
|
|
writeInt(m, ruleSetSeverityAttr)
|
|
|
|
s := b.String()
|
|
return hashcode.String(s)
|
|
}
|
|
|
|
func ruleSetValueChecksum(v interface{}) int {
|
|
b := &bytes.Buffer{}
|
|
b.Grow(defaultHashBufSize)
|
|
|
|
writeBool := func(m map[string]interface{}, attrName string) {
|
|
if v, found := m[attrName]; found {
|
|
fmt.Fprintf(b, "%t", v.(bool))
|
|
}
|
|
}
|
|
|
|
writeDuration := func(m map[string]interface{}, attrName string) {
|
|
if v, found := m[attrName]; found {
|
|
s := v.(string)
|
|
if s != "" {
|
|
d, _ := time.ParseDuration(s)
|
|
fmt.Fprint(b, d.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
writeString := func(m map[string]interface{}, attrName string) {
|
|
if v, found := m[attrName]; found {
|
|
s := strings.TrimSpace(v.(string))
|
|
if s != "" {
|
|
fmt.Fprint(b, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
if v, found := m[ruleSetValueAttr]; found {
|
|
valueMap := v.(map[string]interface{})
|
|
if valueMap != nil {
|
|
writeDuration(valueMap, ruleSetAbsentAttr)
|
|
writeBool(valueMap, ruleSetChangedAttr)
|
|
writeString(valueMap, ruleSetContainsAttr)
|
|
writeString(valueMap, ruleSetMatchAttr)
|
|
writeString(valueMap, ruleSetNotMatchAttr)
|
|
writeString(valueMap, ruleSetMinValueAttr)
|
|
writeString(valueMap, ruleSetNotContainAttr)
|
|
writeString(valueMap, ruleSetMaxValueAttr)
|
|
|
|
if v, found := valueMap[ruleSetOverAttr]; found {
|
|
overMap := v.(map[string]interface{})
|
|
writeDuration(overMap, ruleSetLastAttr)
|
|
writeString(overMap, ruleSetUsingAttr)
|
|
}
|
|
}
|
|
}
|
|
|
|
s := b.String()
|
|
return hashcode.String(s)
|
|
}
|
|
|
|
func ruleSetValueOverChecksum(v interface{}) int {
|
|
b := &bytes.Buffer{}
|
|
b.Grow(defaultHashBufSize)
|
|
|
|
writeString := func(m map[string]interface{}, attrName string) {
|
|
if v, found := m[attrName]; found {
|
|
s := strings.TrimSpace(v.(string))
|
|
if s != "" {
|
|
fmt.Fprint(b, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
m := v.(map[string]interface{})
|
|
|
|
writeString(m, ruleSetLastAttr)
|
|
writeString(m, ruleSetUsingAttr)
|
|
|
|
s := b.String()
|
|
return hashcode.String(s)
|
|
}
|
|
|
|
// ParseConfig reads Terraform config data and stores the information into a
|
|
// Circonus RuleSet object. ParseConfig, ruleSetRead(), and ruleSetChecksum
|
|
// must be kept in sync.
|
|
func (rs *circonusRuleSet) ParseConfig(d *schema.ResourceData) error {
|
|
if v, found := d.GetOk(ruleSetCheckAttr); found {
|
|
rs.CheckCID = v.(string)
|
|
}
|
|
|
|
if v, found := d.GetOk(ruleSetLinkAttr); found {
|
|
s := v.(string)
|
|
rs.Link = &s
|
|
}
|
|
|
|
if v, found := d.GetOk(ruleSetMetricTypeAttr); found {
|
|
rs.MetricType = v.(string)
|
|
}
|
|
|
|
if v, found := d.GetOk(ruleSetNotesAttr); found {
|
|
s := v.(string)
|
|
rs.Notes = &s
|
|
}
|
|
|
|
if v, found := d.GetOk(ruleSetParentAttr); found {
|
|
s := v.(string)
|
|
rs.Parent = &s
|
|
}
|
|
|
|
if v, found := d.GetOk(ruleSetMetricNameAttr); found {
|
|
rs.MetricName = v.(string)
|
|
}
|
|
|
|
rs.Rules = make([]api.RuleSetRule, 0, defaultRuleSetRuleLen)
|
|
if ifListRaw, found := d.GetOk(ruleSetIfAttr); found {
|
|
ifList := ifListRaw.([]interface{})
|
|
for _, ifListElem := range ifList {
|
|
ifAttrs := newInterfaceMap(ifListElem.(map[string]interface{}))
|
|
|
|
rule := api.RuleSetRule{}
|
|
|
|
if thenListRaw, found := ifAttrs[ruleSetThenAttr]; found {
|
|
thenList := thenListRaw.(*schema.Set).List()
|
|
|
|
for _, thenListRaw := range thenList {
|
|
thenAttrs := newInterfaceMap(thenListRaw)
|
|
|
|
if v, found := thenAttrs[ruleSetAfterAttr]; found {
|
|
s := v.(string)
|
|
if s != "" {
|
|
d, err := time.ParseDuration(v.(string))
|
|
if err != nil {
|
|
return errwrap.Wrapf(fmt.Sprintf("unable to parse %q duration %q: {{err}}", ruleSetAfterAttr, v.(string)), err)
|
|
}
|
|
rule.Wait = uint(d.Minutes())
|
|
}
|
|
}
|
|
|
|
// NOTE: break from convention of alpha sorting attributes and handle Notify after Severity
|
|
|
|
if i, found := thenAttrs[ruleSetSeverityAttr]; found {
|
|
rule.Severity = uint(i.(int))
|
|
}
|
|
|
|
if notifyListRaw, found := thenAttrs[ruleSetNotifyAttr]; found {
|
|
notifyList := interfaceList(notifyListRaw.([]interface{}))
|
|
|
|
sev := uint8(rule.Severity)
|
|
for _, contactGroupCID := range notifyList.List() {
|
|
var found bool
|
|
if contactGroups, ok := rs.ContactGroups[sev]; ok {
|
|
for _, contactGroup := range contactGroups {
|
|
if contactGroup == contactGroupCID {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
rs.ContactGroups[sev] = append(rs.ContactGroups[sev], contactGroupCID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ruleSetValueListRaw, found := ifAttrs[ruleSetValueAttr]; found {
|
|
ruleSetValueList := ruleSetValueListRaw.(*schema.Set).List()
|
|
|
|
for _, valueListRaw := range ruleSetValueList {
|
|
valueAttrs := newInterfaceMap(valueListRaw)
|
|
|
|
METRIC_TYPE:
|
|
switch rs.MetricType {
|
|
case ruleSetMetricTypeNumeric:
|
|
if v, found := valueAttrs[ruleSetAbsentAttr]; found {
|
|
s := v.(string)
|
|
if s != "" {
|
|
d, _ := time.ParseDuration(s)
|
|
rule.Criteria = apiRuleSetAbsent
|
|
rule.Value = float64(d.Seconds())
|
|
break METRIC_TYPE
|
|
}
|
|
}
|
|
|
|
if v, found := valueAttrs[ruleSetChangedAttr]; found {
|
|
b := v.(bool)
|
|
if b {
|
|
rule.Criteria = apiRuleSetChanged
|
|
break METRIC_TYPE
|
|
}
|
|
}
|
|
|
|
if v, found := valueAttrs[ruleSetMinValueAttr]; found {
|
|
s := v.(string)
|
|
if s != "" {
|
|
rule.Criteria = apiRuleSetMinValue
|
|
rule.Value = s
|
|
break METRIC_TYPE
|
|
}
|
|
}
|
|
|
|
if v, found := valueAttrs[ruleSetMaxValueAttr]; found {
|
|
s := v.(string)
|
|
if s != "" {
|
|
rule.Criteria = apiRuleSetMaxValue
|
|
rule.Value = s
|
|
break METRIC_TYPE
|
|
}
|
|
}
|
|
case ruleSetMetricTypeText:
|
|
if v, found := valueAttrs[ruleSetAbsentAttr]; found {
|
|
s := v.(string)
|
|
if s != "" {
|
|
d, _ := time.ParseDuration(s)
|
|
rule.Criteria = apiRuleSetAbsent
|
|
rule.Value = float64(d.Seconds())
|
|
break METRIC_TYPE
|
|
}
|
|
}
|
|
|
|
if v, found := valueAttrs[ruleSetChangedAttr]; found {
|
|
b := v.(bool)
|
|
if b {
|
|
rule.Criteria = apiRuleSetChanged
|
|
break METRIC_TYPE
|
|
}
|
|
}
|
|
|
|
if v, found := valueAttrs[ruleSetContainsAttr]; found {
|
|
s := v.(string)
|
|
if s != "" {
|
|
rule.Criteria = apiRuleSetContains
|
|
rule.Value = s
|
|
break METRIC_TYPE
|
|
}
|
|
}
|
|
|
|
if v, found := valueAttrs[ruleSetMatchAttr]; found {
|
|
s := v.(string)
|
|
if s != "" {
|
|
rule.Criteria = apiRuleSetMatch
|
|
rule.Value = s
|
|
break METRIC_TYPE
|
|
}
|
|
}
|
|
|
|
if v, found := valueAttrs[ruleSetNotMatchAttr]; found {
|
|
s := v.(string)
|
|
if s != "" {
|
|
rule.Criteria = apiRuleSetNotMatch
|
|
rule.Value = s
|
|
break METRIC_TYPE
|
|
}
|
|
}
|
|
|
|
if v, found := valueAttrs[ruleSetNotContainAttr]; found {
|
|
s := v.(string)
|
|
if s != "" {
|
|
rule.Criteria = apiRuleSetNotContains
|
|
rule.Value = s
|
|
break METRIC_TYPE
|
|
}
|
|
}
|
|
default:
|
|
return fmt.Errorf("PROVIDER BUG: unsupported rule set metric type: %q", rs.MetricType)
|
|
}
|
|
|
|
if ruleSetOverListRaw, found := valueAttrs[ruleSetOverAttr]; found {
|
|
overList := ruleSetOverListRaw.(*schema.Set).List()
|
|
|
|
for _, overListRaw := range overList {
|
|
overAttrs := newInterfaceMap(overListRaw)
|
|
|
|
if v, found := overAttrs[ruleSetLastAttr]; found {
|
|
last, err := time.ParseDuration(v.(string))
|
|
if err != nil {
|
|
return errwrap.Wrapf(fmt.Sprintf("unable to parse duration %s attribute", ruleSetLastAttr), err)
|
|
}
|
|
rule.WindowingDuration = uint(last.Seconds())
|
|
}
|
|
|
|
if v, found := overAttrs[ruleSetUsingAttr]; found {
|
|
s := v.(string)
|
|
rule.WindowingFunction = &s
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
rs.Rules = append(rs.Rules, rule)
|
|
}
|
|
}
|
|
|
|
if v, found := d.GetOk(ruleSetTagsAttr); found {
|
|
rs.Tags = derefStringList(flattenSet(v.(*schema.Set)))
|
|
}
|
|
|
|
if err := rs.Validate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (rs *circonusRuleSet) Create(ctxt *providerContext) error {
|
|
crs, err := ctxt.client.CreateRuleSet(&rs.RuleSet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rs.CID = crs.CID
|
|
|
|
return nil
|
|
}
|
|
|
|
func (rs *circonusRuleSet) Update(ctxt *providerContext) error {
|
|
_, err := ctxt.client.UpdateRuleSet(&rs.RuleSet)
|
|
if err != nil {
|
|
return errwrap.Wrapf(fmt.Sprintf("Unable to update rule set %s: {{err}}", rs.CID), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (rs *circonusRuleSet) Validate() error {
|
|
// TODO(sean@): From https://login.circonus.com/resources/api/calls/rule_set
|
|
// under `value`:
|
|
//
|
|
// For an 'on absence' rule this is the number of seconds the metric must not
|
|
// have been collected for, and should not be lower than either the period or
|
|
// timeout of the metric being collected.
|
|
|
|
for i, rule := range rs.Rules {
|
|
if rule.Criteria == "" {
|
|
return fmt.Errorf("rule %d for check ID %s has an empty criteria", i, rs.CheckCID)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|