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  }