terraform/builtin/providers/circonus/resource_circonus_rule_set.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
}