Circonus Provider (#12338)

* Begin stubbing out the Circonus provider.

* Remove all references to `reverse:secret_key`.

This value is dynamically set by the service and unused by Terraform.

* Update the `circonus_check` resource.

Still a WIP.

* Add docs for the `circonus_check` resource.

Commit miss, this should have been included in the last commit.

* "Fix" serializing check tags

I still need to figure out how I can make them order agnostic w/o using
a TypeSet.  I'm worried that's what I'm going to have to do.

* Spike a quick circonus_broker data source.

* Convert tags to a Set so the order does not matter.

* Add a `circonus_account` data source.

* Correctly spell account.

Pointed out by: @postwait

* Add the `circonus_contact_group` resource.

* Push descriptions into their own file in order to reduce the busyness of the schema when reviewing code.

* Rename `circonus_broker` and `broker` to `circonus_collector` and `collector`, respectively.

Change made with concent by Circonus to reduce confusion (@postwait, @maier, and several others).

* Use upstream contsants where available.

* Import the latest circonus-gometrics.

* Move to using a Set of collectors vs a list attached to a single attribute.

* Rename "cid" to "id" in the circonus_account data source and elsewhere
where possible.

* Inject a tag automatically.  Update gometrics.

* Checkpoint `circonus_metric` resource.

* Enable provider-level auto-tagging.  This is disabled by default.

* Rearrange metric.  This is an experimental "style" of a provider.  We'll see.

That moment. When you think you've gone off the rails on a mad scientist
experiment but like the outcome and think you may be onto something but
haven't proven it to yourself or anyone else yet?  That.  That exact
feeling of semi-confidence while being alone in the wilderness.  Please
let this not be the Terraform provider equivalent of DJB's C style of
coding.

We'll know in another resource or two if this was a horrible mistake or
not.

* Begin moving `resource_circonus_check` over to the new world order/structure:

Much of this is WIP and incomplete, but here is the new supported
structure:

```
variable "used_metric_name" {
  default = "_usage`0`_used"
}

resource "circonus_check" "usage" {
  # collectors = ["${var.collectors}"]
  collector {
    id = "${var.collectors[0]}"
  }

  name       = "${var.check_name}"
  notes      = "${var.notes}"

  json {
    url = "https://${var.target}/account/current"

    http_headers = {
      "Accept"                = "application/json"
      "X-Circonus-App-Name"   = "TerraformCheck"
      "X-Circonus-Auth-Token" = "${var.api_token}"
    }
  }

  stream {
    name = "${circonus_metric.used.name}"
    tags = "${circonus_metric.used.tags}"
    type = "${circonus_metric.used.type}"
  }

  tags = {
    source = "circonus"
  }
}

resource "circonus_metric" "used" {
  name = "${var.used_metric_name}"

  tags = {
    source = "circonus"
  }

  type = "numeric"
}
```

* Document the `circonus_metric` resource.

* Updated `circonus_check` docs.

* If a port was present, automatically set it in the Config.

* Alpha sort the check parameters now that they've been renamed.

* Fix a handful of panics as a result of the schema changing.

* Move back to a `TypeSet` for tags.  After a stint with `TypeMap`, move
back to `TypeSet`.

A set of strings seems to match the API the best.  The `map` type was
convenient because it reduced the amount of boilerplate, but you loose
out on other things.  For instance, tags come in the form of
`category:value`, so naturally it seems like you could use a map, but
you can't without severe loss of functionality because assigning two
values to the same category is common.  And you can't normalize map
input or suppress the output correctly (this was eventually what broke
the camel's back).  I tried an experiment of normalizing the input to be
`category:value` as the key in the map and a value of `""`, but... seee
diff suppress.  In this case, simple is good.

While here bring some cleanups to _Metric since that was my initial
testing target.

* Rename `providerConfig` to `_ProviderConfig`

* Checkpoint the `json` check type.

* Fix a few residual issues re: missing descriptions.

* Rename `validateRegexp` to `_ValidateRegexp`

* Use tags as real sets, not just a slice of strings.

* Move the DiffSuppressFunc for tags down to the Elem.

* Fix up unit tests to chase the updated, default hasher function being used.

* Remove `Computed` attribute from `TypeSet` objects.

This fixes a pile of issues re: update that I was having.

* Rename functions.

`GetStringOk` -> `GetStringOK`
`GetSetAsListOk` -> `GetSetAsListOK`
`GetIntOk` -> `GetIntOK`

* Various small cleanups and comments rolled into a single commit.

* Add a `postgresql` check type for the `circonus_check` resource.

* Rename various validator functions to be _CapitalCase vs capitalCase.

* Err... finish the validator renames.

* Add `GetFloat64()` support.

* Add `icmp_ping` check type support.

* Catch up to the _API*Attr renames.

Deliberately left out of the previous commit in order to create a clean
example of what is required to add a new check type to the
`circonus_check` resource.

* Clarify when the `target` attribute is required for the `postgresql`
check type.

* Correctly pull the metric ID attribute from the right location.

* Add a circonus_stream_group resource (a.k.a. a Circonus "metric cluster")

* Add support for the [`caql`](https://login.circonus.com/user/docs/caql_reference) check type.

* Add support for the `http` check type.

* `s/SSL/TLS/g`

* Add support for `tcp` check types.

* Enumerate the available metrics that are supported for each check type.

* Add [`cloudwatch`](https://login.circonus.com/user/docs/Data/CheckTypes/CloudWatch) check type support.

* Add a `circonus_trigger` resource (a.k.a Circonus Ruleset).

* Rename a handful of functions to make it clear in the function name the
direction of flow for information moving through the provider.

TL;DR: Replace `parse` and `read` with "foo to bar"-like names.

* Fix the attribute name used in a validator.  Absent != After.

* Set the minimum `absent` predicate to 70s per testing.

* Fix the regression tests for circonus_trigger now that absent has a 70s min

* Fix up the `tcp` check to require a `host` attribute.

Fix tests.  It's clear I didn't run these before committing/pushing the
`tcp` check last time.

* Fix `circonus_check` for `cloudwatch` checks.

* Rename `parsePerCheckTypeConfig()` to `_CheckConfigToAPI` to be
consistent with other function names.

grep(1)ability of code++

* Slack buttons as an integer are string encoded.

* Fix updates for `circonus_contact`.

* Fix the out parameters for contact groups.

* Move to using `_CastSchemaToTF()` where appropriate.

* Fix circonus_contact_group.  Updates work as expected now.

* Use `_StateSet()` in place of `d.Set()` everywhere.

* Make a quick pass over the collector datasource to modernize its style

* Quick pass for items identified by `golint`.

* Fix up collectors

* Fix the `json` check type.

Reconcile possible sources of drift.  Update now works as expected.

* Normalize trigger durations to seconds.

* Improve the robustness of the state handling for the `circonus_contact_group` resource.

* I'm torn on this, but sort the contact groups in the notify list.

This does mean that if the first contact group in the list has a higher
lexical sort order the plan won't converge until the offending resource
is tainted and recreated.  But there's also some sorting happening
elsewhere, so.... sort and taint for now and this will need to be
revisited in the future.

* Add support for the `httptrap` check type.

* Remove empty units from the state file.

* Metric clusters can return a 404.  Detect this accordingly in its
respective Exists handler.

* Add a `circonus_graph` resource.

* Fix a handful of bugs in the graph provider.

* Re-enable the necessary `ConflictsWith` definitions and normalize attribute names.

* Objects that have been deleted via the UI return a 404. Handle in Exists().

* Teach `circonus_graph`'s Stack set to accept nil values.

* Set `ForceNew: true` for a graph's name.

* Chase various API fixes required to make `circonus_graph` work as expected.

* Fix up the handling of sub-1 zoom resolutions for graphs.

* Add the `check_by_collector` out parameter to the `circonus_check` resource.

* Improve validation of line vs area graphs.  Fix graph_style.

* Fix up the `logarithmic` graph axis option.

* Resolve various trivial `go vet` issues.

* Add a stream_group out parameter.

* Remove incorrectly applied `Optional` attributes to the `circonus_account` resource.

* Remove various `Optional` attributes from the `circonus_collector` data source.

* Centralize the common need to suppress leading and trailing whitespace into `suppressWhitespace`.

* Sync up with upstream vendor fixes for circonus_graph.

* Update the checksum value for the http check.

* Chase `circonus_graph`'s underlying `line_style` API object change from `string` to `*string`.

* Clean up tests to use a generic terraform regression testing account.

* Add support for the MySQL to the `circonus_check` resource.

* Begin stubbing out the Circonus provider.

* Remove all references to `reverse:secret_key`.

This value is dynamically set by the service and unused by Terraform.

* Update the `circonus_check` resource.

Still a WIP.

* Add docs for the `circonus_check` resource.

Commit miss, this should have been included in the last commit.

* "Fix" serializing check tags

I still need to figure out how I can make them order agnostic w/o using
a TypeSet.  I'm worried that's what I'm going to have to do.

* Spike a quick circonus_broker data source.

* Convert tags to a Set so the order does not matter.

* Add a `circonus_account` data source.

* Correctly spell account.

Pointed out by: @postwait

* Add the `circonus_contact_group` resource.

* Push descriptions into their own file in order to reduce the busyness of the schema when reviewing code.

* Rename `circonus_broker` and `broker` to `circonus_collector` and `collector`, respectively.

Change made with concent by Circonus to reduce confusion (@postwait, @maier, and several others).

* Use upstream contsants where available.

* Import the latest circonus-gometrics.

* Move to using a Set of collectors vs a list attached to a single attribute.

* Rename "cid" to "id" in the circonus_account data source and elsewhere
where possible.

* Inject a tag automatically.  Update gometrics.

* Checkpoint `circonus_metric` resource.

* Enable provider-level auto-tagging.  This is disabled by default.

* Rearrange metric.  This is an experimental "style" of a provider.  We'll see.

That moment. When you think you've gone off the rails on a mad scientist
experiment but like the outcome and think you may be onto something but
haven't proven it to yourself or anyone else yet?  That.  That exact
feeling of semi-confidence while being alone in the wilderness.  Please
let this not be the Terraform provider equivalent of DJB's C style of
coding.

We'll know in another resource or two if this was a horrible mistake or
not.

* Begin moving `resource_circonus_check` over to the new world order/structure:

Much of this is WIP and incomplete, but here is the new supported
structure:

```
variable "used_metric_name" {
  default = "_usage`0`_used"
}

resource "circonus_check" "usage" {
  # collectors = ["${var.collectors}"]
  collector {
    id = "${var.collectors[0]}"
  }

  name       = "${var.check_name}"
  notes      = "${var.notes}"

  json {
    url = "https://${var.target}/account/current"

    http_headers = {
      "Accept"                = "application/json"
      "X-Circonus-App-Name"   = "TerraformCheck"
      "X-Circonus-Auth-Token" = "${var.api_token}"
    }
  }

  stream {
    name = "${circonus_metric.used.name}"
    tags = "${circonus_metric.used.tags}"
    type = "${circonus_metric.used.type}"
  }

  tags = {
    source = "circonus"
  }
}

resource "circonus_metric" "used" {
  name = "${var.used_metric_name}"

  tags = {
    source = "circonus"
  }

  type = "numeric"
}
```

* Document the `circonus_metric` resource.

* Updated `circonus_check` docs.

* If a port was present, automatically set it in the Config.

* Alpha sort the check parameters now that they've been renamed.

* Fix a handful of panics as a result of the schema changing.

* Move back to a `TypeSet` for tags.  After a stint with `TypeMap`, move
back to `TypeSet`.

A set of strings seems to match the API the best.  The `map` type was
convenient because it reduced the amount of boilerplate, but you loose
out on other things.  For instance, tags come in the form of
`category:value`, so naturally it seems like you could use a map, but
you can't without severe loss of functionality because assigning two
values to the same category is common.  And you can't normalize map
input or suppress the output correctly (this was eventually what broke
the camel's back).  I tried an experiment of normalizing the input to be
`category:value` as the key in the map and a value of `""`, but... seee
diff suppress.  In this case, simple is good.

While here bring some cleanups to _Metric since that was my initial
testing target.

* Rename `providerConfig` to `_ProviderConfig`

* Checkpoint the `json` check type.

* Fix a few residual issues re: missing descriptions.

* Rename `validateRegexp` to `_ValidateRegexp`

* Use tags as real sets, not just a slice of strings.

* Move the DiffSuppressFunc for tags down to the Elem.

* Fix up unit tests to chase the updated, default hasher function being used.

* Remove `Computed` attribute from `TypeSet` objects.

This fixes a pile of issues re: update that I was having.

* Rename functions.

`GetStringOk` -> `GetStringOK`
`GetSetAsListOk` -> `GetSetAsListOK`
`GetIntOk` -> `GetIntOK`

* Various small cleanups and comments rolled into a single commit.

* Add a `postgresql` check type for the `circonus_check` resource.

* Rename various validator functions to be _CapitalCase vs capitalCase.

* Err... finish the validator renames.

* Add `GetFloat64()` support.

* Add `icmp_ping` check type support.

* Catch up to the _API*Attr renames.

Deliberately left out of the previous commit in order to create a clean
example of what is required to add a new check type to the
`circonus_check` resource.

* Clarify when the `target` attribute is required for the `postgresql`
check type.

* Correctly pull the metric ID attribute from the right location.

* Add a circonus_stream_group resource (a.k.a. a Circonus "metric cluster")

* Add support for the [`caql`](https://login.circonus.com/user/docs/caql_reference) check type.

* Add support for the `http` check type.

* `s/SSL/TLS/g`

* Add support for `tcp` check types.

* Enumerate the available metrics that are supported for each check type.

* Add [`cloudwatch`](https://login.circonus.com/user/docs/Data/CheckTypes/CloudWatch) check type support.

* Add a `circonus_trigger` resource (a.k.a Circonus Ruleset).

* Rename a handful of functions to make it clear in the function name the
direction of flow for information moving through the provider.

TL;DR: Replace `parse` and `read` with "foo to bar"-like names.

* Fix the attribute name used in a validator.  Absent != After.

* Set the minimum `absent` predicate to 70s per testing.

* Fix the regression tests for circonus_trigger now that absent has a 70s min

* Fix up the `tcp` check to require a `host` attribute.

Fix tests.  It's clear I didn't run these before committing/pushing the
`tcp` check last time.

* Fix `circonus_check` for `cloudwatch` checks.

* Rename `parsePerCheckTypeConfig()` to `_CheckConfigToAPI` to be
consistent with other function names.

grep(1)ability of code++

* Slack buttons as an integer are string encoded.

* Fix updates for `circonus_contact`.

* Fix the out parameters for contact groups.

* Move to using `_CastSchemaToTF()` where appropriate.

* Fix circonus_contact_group.  Updates work as expected now.

* Use `_StateSet()` in place of `d.Set()` everywhere.

* Make a quick pass over the collector datasource to modernize its style

* Quick pass for items identified by `golint`.

* Fix up collectors

* Fix the `json` check type.

Reconcile possible sources of drift.  Update now works as expected.

* Normalize trigger durations to seconds.

* Improve the robustness of the state handling for the `circonus_contact_group` resource.

* I'm torn on this, but sort the contact groups in the notify list.

This does mean that if the first contact group in the list has a higher
lexical sort order the plan won't converge until the offending resource
is tainted and recreated.  But there's also some sorting happening
elsewhere, so.... sort and taint for now and this will need to be
revisited in the future.

* Add support for the `httptrap` check type.

* Remove empty units from the state file.

* Metric clusters can return a 404.  Detect this accordingly in its
respective Exists handler.

* Add a `circonus_graph` resource.

* Fix a handful of bugs in the graph provider.

* Re-enable the necessary `ConflictsWith` definitions and normalize attribute names.

* Objects that have been deleted via the UI return a 404. Handle in Exists().

* Teach `circonus_graph`'s Stack set to accept nil values.

* Set `ForceNew: true` for a graph's name.

* Chase various API fixes required to make `circonus_graph` work as expected.

* Fix up the handling of sub-1 zoom resolutions for graphs.

* Add the `check_by_collector` out parameter to the `circonus_check` resource.

* Improve validation of line vs area graphs.  Fix graph_style.

* Fix up the `logarithmic` graph axis option.

* Resolve various trivial `go vet` issues.

* Add a stream_group out parameter.

* Remove incorrectly applied `Optional` attributes to the `circonus_account` resource.

* Remove various `Optional` attributes from the `circonus_collector` data source.

* Centralize the common need to suppress leading and trailing whitespace into `suppressWhitespace`.

* Sync up with upstream vendor fixes for circonus_graph.

* Update the checksum value for the http check.

* Chase `circonus_graph`'s underlying `line_style` API object change from `string` to `*string`.

* Clean up tests to use a generic terraform regression testing account.

* Rename all identifiers that began with a `_` and replace with a corresponding lowercase glyph.

* Remove stale comment in types.

* Move the calls to `ResourceData`'s `SetId()` calls to be first in the
list so that no resources are lost in the event of a `panic()`.

* Remove `stateSet` from the `circonus_trigger` resource.

* Remove `stateSet` from the `circonus_stream_group` resource.

* Remove `schemaSet` from the `circonus_graph` resource.

* Remove `stateSet` from the `circonus_contact` resource.

* Remove `stateSet` from the `circonus_metric` resource.

* Remove `stateSet` from the `circonus_account` data source.

* Remove `stateSet` from the `circonus_collector` data source.

* Remove stray `stateSet` call from the `circonus_contact` resource.

This is an odd artifact to find... I'm completely unsure as to why it
was there to begin with but am mostly certain it's a bug and needs to be
removed.

* Remove `stateSet` from the `circonus_check` resource.

* Remove the `stateSet` helper function.

All call sites have been converted to return errors vs `panic()`'ing at
runtime.

* Remove a pile of unused functions and type definitions.

* Remove the last of the `attrReader` interface.

* Remove an unused `Sprintf` call.

* Update `circonus-gometrics` and remove unused files.

* Document what `convertToHelperSchema()` does.

Rename `castSchemaToTF` to `convertToHelperSchema`.

Change the function parameter ordering so the `map` of attribute
descriptions: this is much easier to maintain when the description map
is first when creating schema inline.

* Move descriptions into their respective source files.

* Remove all instances of `panic()`.

In the case of software bugs, log an error.  Never `panic()` and always
return a value.

* Rename `stream_group` to `metric_cluster`.

* Rename triggers to rule sets

* Rename `stream` to `metric`.

* Chase the `stream` -> `metric` change into the docs.

* Remove some unused test functions.

* Add the now required `color` attribute for graphing a `metric_cluster`.

* Add a missing description to silence a warning.

* Add `id` as a selector for the account data source.

* Futureproof testing: Randomize all asset names to prevent any possible resource conflicts.

This isn't a necessary change for our current build and regression
testing, but *just in case* we have a radical change to our testing
framework in the future, make all resource names fully random.

* Rename various values to match the Circonus docs.

* s/alarm/alert/g

* Ensure ruleset criteria can not be empty.
This commit is contained in:
Sean Chittenden 2017-03-10 12:19:17 -08:00 committed by Clint
parent 79736577ba
commit 17fb98afa2
87 changed files with 17812 additions and 0 deletions

View File

@ -0,0 +1,3 @@
# env TESTARGS='-test.parallel=1 -run TestAccCirconusCheckBundle' TF_LOG=debug make test
test::
2>&1 make -C ../../.. testacc TEST=./builtin/providers/circonus | tee test.log

View File

@ -0,0 +1,122 @@
package circonus
import (
"fmt"
"log"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/circonus-labs/circonus-gometrics/api/config"
"github.com/hashicorp/errwrap"
)
// The circonusCheck type is the backing store of the `circonus_check` resource.
type circonusCheck struct {
api.CheckBundle
}
type circonusCheckType string
const (
// CheckBundle.Status can be one of these values
checkStatusActive = "active"
checkStatusDisabled = "disabled"
)
const (
apiCheckTypeCAQL circonusCheckType = "caql"
apiCheckTypeICMPPing circonusCheckType = "ping_icmp"
apiCheckTypeHTTP circonusCheckType = "http"
apiCheckTypeJSON circonusCheckType = "json"
apiCheckTypeMySQL circonusCheckType = "mysql"
apiCheckTypePostgreSQL circonusCheckType = "postgres"
apiCheckTypeTCP circonusCheckType = "tcp"
)
func newCheck() circonusCheck {
return circonusCheck{
CheckBundle: *api.NewCheckBundle(),
}
}
func loadCheck(ctxt *providerContext, cid api.CIDType) (circonusCheck, error) {
var c circonusCheck
cb, err := ctxt.client.FetchCheckBundle(cid)
if err != nil {
return circonusCheck{}, err
}
c.CheckBundle = *cb
return c, nil
}
func checkAPIStatusToBool(s string) bool {
var active bool
switch s {
case checkStatusActive:
active = true
case checkStatusDisabled:
active = false
default:
log.Printf("[ERROR] PROVIDER BUG: check status %q unsupported", s)
}
return active
}
func checkActiveToAPIStatus(active bool) string {
if active {
return checkStatusActive
}
return checkStatusDisabled
}
func (c *circonusCheck) Create(ctxt *providerContext) error {
cb, err := ctxt.client.CreateCheckBundle(&c.CheckBundle)
if err != nil {
return err
}
c.CID = cb.CID
return nil
}
func (c *circonusCheck) Update(ctxt *providerContext) error {
_, err := ctxt.client.UpdateCheckBundle(&c.CheckBundle)
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to update check bundle %s: {{err}}", c.CID), err)
}
return nil
}
func (c *circonusCheck) Fixup() error {
switch apiCheckType(c.Type) {
case apiCheckTypeCloudWatchAttr:
switch c.Period {
case 60:
c.Config[config.Granularity] = "1"
case 300:
c.Config[config.Granularity] = "5"
}
}
return nil
}
func (c *circonusCheck) Validate() error {
if c.Timeout > float32(c.Period) {
return fmt.Errorf("Timeout (%f) can not exceed period (%d)", c.Timeout, c.Period)
}
switch apiCheckType(c.Type) {
case apiCheckTypeCloudWatchAttr:
if !(c.Period == 60 || c.Period == 300) {
return fmt.Errorf("Period must be either 1m or 5m for a %s check", apiCheckTypeCloudWatchAttr)
}
}
return nil
}

View File

@ -0,0 +1,127 @@
package circonus
const (
// Provider-level constants
// defaultAutoTag determines the default behavior of circonus.auto_tag.
defaultAutoTag = false
// When auto_tag is enabled, the default tag category and value will be set to
// the following value unless overriden.
defaultCirconusTag circonusTag = "author:terraform"
// When hashing a Set, default to a buffer this size
defaultHashBufSize = 512
providerAPIURLAttr = "api_url"
providerAutoTagAttr = "auto_tag"
providerKeyAttr = "key"
defaultCheckJSONMethod = "GET"
defaultCheckJSONPort = "443"
defaultCheckJSONVersion = "1.1"
defaultCheckICMPPingAvailability = 100.0
defaultCheckICMPPingCount = 5
defaultCheckICMPPingInterval = "2s"
defaultCheckCAQLTarget = "q._caql"
defaultCheckHTTPCodeRegexp = `^200$`
defaultCheckHTTPMethod = "GET"
defaultCheckHTTPVersion = "1.1"
defaultCheckHTTPTrapAsync = false
defaultCheckCloudWatchVersion = "2010-08-01"
defaultCollectorDetailAttrs = 10
defaultGraphDatapoints = 8
defaultGraphLineStyle = "stepped"
defaultGraphStyle = "line"
defaultGraphFunction = "gauge"
metricUnit = ""
metricUnitRegexp = `^.*$`
defaultRuleSetLast = "300s"
defaultRuleSetMetricType = "numeric"
defaultRuleSetRuleLen = 4
defaultAlertSeverity = 1
defaultRuleSetWindowFunc = "average"
ruleSetAbsentMin = "70s"
)
// Consts and their close relative, Go pseudo-consts.
// validMetricTypes: See `type`: https://login.circonus.com/resources/api/calls/check_bundle
var validMetricTypes = validStringValues{
`caql`,
`composite`,
`histogram`,
`numeric`,
`text`,
}
// validAggregateFuncs: See `aggregate_function`: https://login.circonus.com/resources/api/calls/graph
var validAggregateFuncs = validStringValues{
`none`,
`min`,
`max`,
`sum`,
`mean`,
`geometric_mean`,
}
// validGraphLineStyles: See `line_style`: https://login.circonus.com/resources/api/calls/graph
var validGraphLineStyles = validStringValues{
`stepped`,
`interpolated`,
}
// validGraphStyles: See `style`: https://login.circonus.com/resources/api/calls/graph
var validGraphStyles = validStringValues{
`area`,
`line`,
}
// validAxisAttrs: See `line_style`: https://login.circonus.com/resources/api/calls/graph
var validAxisAttrs = validStringValues{
`left`,
`right`,
}
// validGraphFunctionValues: See `derive`: https://login.circonus.com/resources/api/calls/graph
var validGraphFunctionValues = validStringValues{
`counter`,
`derive`,
`gauge`,
}
// validRuleSetWindowFuncs: See `derive` or `windowing_func`: https://login.circonus.com/resources/api/calls/rule_set
var validRuleSetWindowFuncs = validStringValues{
`average`,
`stddev`,
`derive`,
`derive_stddev`,
`counter`,
`counter_stddev`,
`derive_2`,
`derive_2_stddev`,
`counter_2`,
`counter_2_stddev`,
}
const (
// Supported circonus_trigger.metric_types. See `metric_type`:
// https://login.circonus.com/resources/api/calls/rule_set
ruleSetMetricTypeNumeric = "numeric"
ruleSetMetricTypeText = "text"
)
// validRuleSetMetricTypes: See `metric_type`: https://login.circonus.com/resources/api/calls/rule_set
var validRuleSetMetricTypes = validStringValues{
ruleSetMetricTypeNumeric,
ruleSetMetricTypeText,
}

View File

@ -0,0 +1,271 @@
package circonus
import (
"fmt"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/circonus-labs/circonus-gometrics/api/config"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)
const (
accountAddress1Attr = "address1"
accountAddress2Attr = "address2"
accountCCEmailAttr = "cc_email"
accountCityAttr = "city"
accountContactGroupsAttr = "contact_groups"
accountCountryAttr = "country"
accountCurrentAttr = "current"
accountDescriptionAttr = "description"
accountEmailAttr = "email"
accountIDAttr = "id"
accountInvitesAttr = "invites"
accountLimitAttr = "limit"
accountNameAttr = "name"
accountOwnerAttr = "owner"
accountRoleAttr = "role"
accountStateProvAttr = "state"
accountTimezoneAttr = "timezone"
accountTypeAttr = "type"
accountUIBaseURLAttr = "ui_base_url"
accountUsageAttr = "usage"
accountUsedAttr = "used"
accountUserIDAttr = "id"
accountUsersAttr = "users"
)
var accountDescription = map[schemaAttr]string{
accountContactGroupsAttr: "Contact Groups in this account",
accountInvitesAttr: "Outstanding invites attached to the account",
accountUsageAttr: "Account's usage limits",
accountUsersAttr: "Users attached to this account",
}
func dataSourceCirconusAccount() *schema.Resource {
return &schema.Resource{
Read: dataSourceCirconusAccountRead,
Schema: map[string]*schema.Schema{
accountAddress1Attr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountAddress1Attr],
},
accountAddress2Attr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountAddress2Attr],
},
accountCCEmailAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountCCEmailAttr],
},
accountIDAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ConflictsWith: []string{accountCurrentAttr},
ValidateFunc: validateFuncs(
validateRegexp(accountIDAttr, config.AccountCIDRegex),
),
Description: accountDescription[accountIDAttr],
},
accountCityAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountCityAttr],
},
accountContactGroupsAttr: &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Description: accountDescription[accountContactGroupsAttr],
},
accountCountryAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountCountryAttr],
},
accountCurrentAttr: &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Computed: true,
ConflictsWith: []string{accountIDAttr},
Description: accountDescription[accountCurrentAttr],
},
accountDescriptionAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountDescriptionAttr],
},
accountInvitesAttr: &schema.Schema{
Type: schema.TypeList,
Computed: true,
Description: accountDescription[accountInvitesAttr],
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
accountEmailAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountEmailAttr],
},
accountRoleAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountRoleAttr],
},
},
},
},
accountNameAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountNameAttr],
},
accountOwnerAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountOwnerAttr],
},
accountStateProvAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountStateProvAttr],
},
accountTimezoneAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountTimezoneAttr],
},
accountUIBaseURLAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountUIBaseURLAttr],
},
accountUsageAttr: &schema.Schema{
Type: schema.TypeList,
Computed: true,
Description: accountDescription[accountUsageAttr],
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
accountLimitAttr: &schema.Schema{
Type: schema.TypeInt,
Computed: true,
Description: accountDescription[accountLimitAttr],
},
accountTypeAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountTypeAttr],
},
accountUsedAttr: &schema.Schema{
Type: schema.TypeInt,
Computed: true,
Description: accountDescription[accountUsedAttr],
},
},
},
},
accountUsersAttr: &schema.Schema{
Type: schema.TypeList,
Computed: true,
Description: accountDescription[accountUsersAttr],
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
accountUserIDAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountUserIDAttr],
},
accountRoleAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: accountDescription[accountRoleAttr],
},
},
},
},
},
}
}
func dataSourceCirconusAccountRead(d *schema.ResourceData, meta interface{}) error {
c := meta.(*providerContext)
var cid string
var a *api.Account
var err error
if v, ok := d.GetOk(accountIDAttr); ok {
cid = v.(string)
}
if v, ok := d.GetOk(accountCurrentAttr); ok {
if v.(bool) {
cid = ""
}
}
a, err = c.client.FetchAccount(api.CIDType(&cid))
if err != nil {
return err
}
invitesList := make([]interface{}, 0, len(a.Invites))
for i := range a.Invites {
invitesList = append(invitesList, map[string]interface{}{
accountEmailAttr: a.Invites[i].Email,
accountRoleAttr: a.Invites[i].Role,
})
}
usageList := make([]interface{}, 0, len(a.Usage))
for i := range a.Usage {
usageList = append(usageList, map[string]interface{}{
accountLimitAttr: a.Usage[i].Limit,
accountTypeAttr: a.Usage[i].Type,
accountUsedAttr: a.Usage[i].Used,
})
}
usersList := make([]interface{}, 0, len(a.Users))
for i := range a.Users {
usersList = append(usersList, map[string]interface{}{
accountUserIDAttr: a.Users[i].UserCID,
accountRoleAttr: a.Users[i].Role,
})
}
d.SetId(a.CID)
d.Set(accountAddress1Attr, a.Address1)
d.Set(accountAddress2Attr, a.Address2)
d.Set(accountCCEmailAttr, a.CCEmail)
d.Set(accountIDAttr, a.CID)
d.Set(accountCityAttr, a.City)
d.Set(accountContactGroupsAttr, a.ContactGroups)
d.Set(accountCountryAttr, a.Country)
d.Set(accountDescriptionAttr, a.Description)
if err := d.Set(accountInvitesAttr, invitesList); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store account %q attribute: {{err}}", accountInvitesAttr), err)
}
d.Set(accountNameAttr, a.Name)
d.Set(accountOwnerAttr, a.OwnerCID)
d.Set(accountStateProvAttr, a.StateProv)
d.Set(accountTimezoneAttr, a.Timezone)
d.Set(accountUIBaseURLAttr, a.UIBaseURL)
if err := d.Set(accountUsageAttr, usageList); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store account %q attribute: {{err}}", accountUsageAttr), err)
}
if err := d.Set(accountUsersAttr, usersList); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store account %q attribute: {{err}}", accountUsersAttr), err)
}
return nil
}

View File

@ -0,0 +1,66 @@
package circonus
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccDataSourceCirconusAccount(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDataSourceCirconusAccountCurrentConfig,
Check: resource.ComposeTestCheckFunc(
testAccDataSourceCirconusAccountCheck("data.circonus_account.by_current", "/account/3081"),
),
},
},
})
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDataSourceCirconusAccountIDConfig,
Check: resource.ComposeTestCheckFunc(
testAccDataSourceCirconusAccountCheck("data.circonus_account.by_id", "/account/3081"),
),
},
},
})
}
func testAccDataSourceCirconusAccountCheck(name, cid string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("root module has no resource called %s", name)
}
attr := rs.Primary.Attributes
if attr[accountIDAttr] != cid {
return fmt.Errorf("bad %s %s", accountIDAttr, attr[accountIDAttr])
}
return nil
}
}
const testAccDataSourceCirconusAccountCurrentConfig = `
data "circonus_account" "by_current" {
current = true
}
`
const testAccDataSourceCirconusAccountIDConfig = `
data "circonus_account" "by_id" {
id = "/account/3081"
}
`

View File

@ -0,0 +1,214 @@
package circonus
import (
"fmt"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/circonus-labs/circonus-gometrics/api/config"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)
const (
collectorCNAttr = "cn"
collectorIDAttr = "id"
collectorDetailsAttr = "details"
collectorExternalHostAttr = "external_host"
collectorExternalPortAttr = "external_port"
collectorIPAttr = "ip"
collectorLatitudeAttr = "latitude"
collectorLongitudeAttr = "longitude"
collectorMinVersionAttr = "min_version"
collectorModulesAttr = "modules"
collectorNameAttr = "name"
collectorPortAttr = "port"
collectorSkewAttr = "skew"
collectorStatusAttr = "status"
collectorTagsAttr = "tags"
collectorTypeAttr = "type"
collectorVersionAttr = "version"
)
var collectorDescription = map[schemaAttr]string{
collectorDetailsAttr: "Details associated with individual collectors (a.k.a. broker)",
collectorTagsAttr: "Tags assigned to a collector",
}
func dataSourceCirconusCollector() *schema.Resource {
return &schema.Resource{
Read: dataSourceCirconusCollectorRead,
Schema: map[string]*schema.Schema{
collectorDetailsAttr: &schema.Schema{
Type: schema.TypeList,
Computed: true,
Description: collectorDescription[collectorDetailsAttr],
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
collectorCNAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: collectorDescription[collectorCNAttr],
},
collectorExternalHostAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: collectorDescription[collectorExternalHostAttr],
},
collectorExternalPortAttr: &schema.Schema{
Type: schema.TypeInt,
Computed: true,
Description: collectorDescription[collectorExternalPortAttr],
},
collectorIPAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: collectorDescription[collectorIPAttr],
},
collectorMinVersionAttr: &schema.Schema{
Type: schema.TypeInt,
Computed: true,
Description: collectorDescription[collectorMinVersionAttr],
},
collectorModulesAttr: &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Description: collectorDescription[collectorModulesAttr],
},
collectorPortAttr: &schema.Schema{
Type: schema.TypeInt,
Computed: true,
Description: collectorDescription[collectorPortAttr],
},
collectorSkewAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: collectorDescription[collectorSkewAttr],
},
collectorStatusAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: collectorDescription[collectorStatusAttr],
},
collectorVersionAttr: &schema.Schema{
Type: schema.TypeInt,
Computed: true,
Description: collectorDescription[collectorVersionAttr],
},
},
},
},
collectorIDAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validateRegexp(collectorIDAttr, config.BrokerCIDRegex),
Description: collectorDescription[collectorIDAttr],
},
collectorLatitudeAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: collectorDescription[collectorLatitudeAttr],
},
collectorLongitudeAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: collectorDescription[collectorLongitudeAttr],
},
collectorNameAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: collectorDescription[collectorNameAttr],
},
collectorTagsAttr: tagMakeConfigSchema(collectorTagsAttr),
collectorTypeAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: collectorDescription[collectorTypeAttr],
},
},
}
}
func dataSourceCirconusCollectorRead(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
var collector *api.Broker
var err error
cid := d.Id()
if cidRaw, ok := d.GetOk(collectorIDAttr); ok {
cid = cidRaw.(string)
}
collector, err = ctxt.client.FetchBroker(api.CIDType(&cid))
if err != nil {
return err
}
d.SetId(collector.CID)
if err := d.Set(collectorDetailsAttr, collectorDetailsToState(collector)); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store collector %q attribute: {{err}}", collectorDetailsAttr), err)
}
d.Set(collectorIDAttr, collector.CID)
d.Set(collectorLatitudeAttr, collector.Latitude)
d.Set(collectorLongitudeAttr, collector.Longitude)
d.Set(collectorNameAttr, collector.Name)
d.Set(collectorTagsAttr, collector.Tags)
d.Set(collectorTypeAttr, collector.Type)
return nil
}
func collectorDetailsToState(c *api.Broker) []interface{} {
details := make([]interface{}, 0, len(c.Details))
for _, collector := range c.Details {
collectorDetails := make(map[string]interface{}, defaultCollectorDetailAttrs)
collectorDetails[collectorCNAttr] = collector.CN
if collector.ExternalHost != nil {
collectorDetails[collectorExternalHostAttr] = *collector.ExternalHost
}
if collector.ExternalPort != 0 {
collectorDetails[collectorExternalPortAttr] = collector.ExternalPort
}
if collector.IP != nil {
collectorDetails[collectorIPAttr] = *collector.IP
}
if collector.MinVer != 0 {
collectorDetails[collectorMinVersionAttr] = collector.MinVer
}
if len(collector.Modules) > 0 {
collectorDetails[collectorModulesAttr] = collector.Modules
}
if collector.Port != nil {
collectorDetails[collectorPortAttr] = *collector.Port
}
if collector.Skew != nil {
collectorDetails[collectorSkewAttr] = *collector.Skew
}
if collector.Status != "" {
collectorDetails[collectorStatusAttr] = collector.Status
}
if collector.Version != nil {
collectorDetails[collectorVersionAttr] = *collector.Version
}
details = append(details, collectorDetails)
}
return details
}

View File

@ -0,0 +1,47 @@
package circonus
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccDataSourceCirconusCollector(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDataSourceCirconusCollectorConfig,
Check: resource.ComposeTestCheckFunc(
testAccDataSourceCirconusCollectorCheck("data.circonus_collector.by_id", "/broker/1"),
),
},
},
})
}
func testAccDataSourceCirconusCollectorCheck(name, cid string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("root module has no resource called %s", name)
}
attr := rs.Primary.Attributes
if attr[collectorIDAttr] != cid {
return fmt.Errorf("bad id %s", attr[collectorIDAttr])
}
return nil
}
}
const testAccDataSourceCirconusCollectorConfig = `
data "circonus_collector" "by_id" {
id = "/broker/1"
}
`

View File

@ -0,0 +1,83 @@
package circonus
import "log"
type interfaceList []interface{}
type interfaceMap map[string]interface{}
// newInterfaceMap returns a helper type that has methods for common operations
// for accessing data.
func newInterfaceMap(l interface{}) interfaceMap {
return interfaceMap(l.(map[string]interface{}))
}
// CollectList returns []string of values that matched the key attrName.
// interfaceList most likely came from a schema.TypeSet.
func (l interfaceList) CollectList(attrName schemaAttr) []string {
stringList := make([]string, 0, len(l))
for _, mapRaw := range l {
mapAttrs := mapRaw.(map[string]interface{})
if v, ok := mapAttrs[string(attrName)]; ok {
stringList = append(stringList, v.(string))
}
}
return stringList
}
// List returns a list of values in a Set as a string slice
func (l interfaceList) List() []string {
stringList := make([]string, 0, len(l))
for _, e := range l {
switch e.(type) {
case string:
stringList = append(stringList, e.(string))
case []interface{}:
for _, v := range e.([]interface{}) {
stringList = append(stringList, v.(string))
}
default:
log.Printf("[ERROR] PROVIDER BUG: unable to convert %#v to list", e)
return nil
}
}
return stringList
}
// CollectList returns []string of values that matched the key attrName.
// interfaceMap most likely came from a schema.TypeSet.
func (m interfaceMap) CollectList(attrName schemaAttr) []string {
stringList := make([]string, 0, len(m))
for _, mapRaw := range m {
mapAttrs := mapRaw.(map[string]interface{})
if v, ok := mapAttrs[string(attrName)]; ok {
stringList = append(stringList, v.(string))
}
}
return stringList
}
// CollectMap returns map[string]string of values that matched the key attrName.
// interfaceMap most likely came from a schema.TypeSet.
func (m interfaceMap) CollectMap(attrName schemaAttr) map[string]string {
var mergedMap map[string]string
if attrRaw, ok := m[string(attrName)]; ok {
attrMap := attrRaw.(map[string]interface{})
mergedMap = make(map[string]string, len(m))
for k, v := range attrMap {
mergedMap[k] = v.(string)
}
}
if len(mergedMap) == 0 {
return nil
}
return mergedMap
}

View File

@ -0,0 +1,182 @@
package circonus
// The circonusMetric type is the backing store of the `circonus_metric` resource.
import (
"bytes"
"fmt"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/hashicorp/errwrap"
uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
)
type circonusMetric struct {
ID metricID
api.CheckBundleMetric
}
func newMetric() circonusMetric {
return circonusMetric{}
}
func (m *circonusMetric) Create(d *schema.ResourceData) error {
return m.SaveState(d)
}
func (m *circonusMetric) ParseConfig(id string, d *schema.ResourceData) error {
m.ID = metricID(id)
if v, found := d.GetOk(metricNameAttr); found {
m.Name = v.(string)
}
if v, found := d.GetOk(metricActiveAttr); found {
m.Status = metricActiveToAPIStatus(v.(bool))
}
if v, found := d.GetOk(metricTagsAttr); found {
m.Tags = derefStringList(flattenSet(v.(*schema.Set)))
}
if v, found := d.GetOk(metricTypeAttr); found {
m.Type = v.(string)
}
if v, found := d.GetOk(metricUnitAttr); found {
s := v.(string)
m.Units = &s
}
if m.Units != nil && *m.Units == "" {
m.Units = nil
}
return nil
}
func (m *circonusMetric) ParseConfigMap(id string, attrMap map[string]interface{}) error {
m.ID = metricID(id)
if v, found := attrMap[metricNameAttr]; found {
m.Name = v.(string)
}
if v, found := attrMap[metricActiveAttr]; found {
m.Status = metricActiveToAPIStatus(v.(bool))
}
if v, found := attrMap[metricTagsAttr]; found {
m.Tags = derefStringList(flattenSet(v.(*schema.Set)))
}
if v, found := attrMap[metricTypeAttr]; found {
m.Type = v.(string)
}
if v, found := attrMap[metricUnitAttr]; found {
s := v.(string)
m.Units = &s
}
if m.Units != nil && *m.Units == "" {
m.Units = nil
}
return nil
}
func (m *circonusMetric) SaveState(d *schema.ResourceData) error {
d.SetId(string(m.ID))
d.Set(metricActiveAttr, metricAPIStatusToBool(m.Status))
d.Set(metricNameAttr, m.Name)
d.Set(metricTagsAttr, tagsToState(apiToTags(m.Tags)))
d.Set(metricTypeAttr, m.Type)
d.Set(metricUnitAttr, indirect(m.Units))
return nil
}
func (m *circonusMetric) Update(d *schema.ResourceData) error {
// NOTE: there are no "updates" to be made against an API server, so we just
// pass through a call to SaveState. Keep this method around for API
// symmetry.
return m.SaveState(d)
}
func metricAPIStatusToBool(s string) bool {
switch s {
case metricStatusActive:
return true
case metricStatusAvailable:
return false
default:
// log.Printf("PROVIDER BUG: metric status %q unsupported", s)
return false
}
}
func metricActiveToAPIStatus(active bool) string {
if active {
return metricStatusActive
}
return metricStatusAvailable
}
func newMetricID() (string, error) {
id, err := uuid.GenerateUUID()
if err != nil {
return "", errwrap.Wrapf("metric ID creation failed: {{err}}", err)
}
return id, nil
}
func metricChecksum(m interfaceMap) int {
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
// Order writes to the buffer using lexically sorted list for easy visual
// reconciliation with other lists.
if v, found := m[metricActiveAttr]; found {
fmt.Fprintf(b, "%t", v.(bool))
}
if v, found := m[metricNameAttr]; found {
fmt.Fprint(b, v.(string))
}
if v, found := m[metricTagsAttr]; found {
tags := derefStringList(flattenSet(v.(*schema.Set)))
for _, tag := range tags {
fmt.Fprint(b, tag)
}
}
if v, found := m[metricTypeAttr]; found {
fmt.Fprint(b, v.(string))
}
if v, found := m[metricUnitAttr]; found {
if v != nil {
var s string
switch v.(type) {
case string:
s = v.(string)
case *string:
s = *v.(*string)
}
if s != "" {
fmt.Fprint(b, s)
}
}
}
s := b.String()
return hashcode.String(s)
}

View File

@ -0,0 +1,57 @@
package circonus
import (
"fmt"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/hashicorp/errwrap"
)
type circonusMetricCluster struct {
api.MetricCluster
}
func newMetricCluster() circonusMetricCluster {
return circonusMetricCluster{
MetricCluster: api.MetricCluster{},
}
}
func loadMetricCluster(ctxt *providerContext, cid api.CIDType) (circonusMetricCluster, error) {
var mc circonusMetricCluster
cmc, err := ctxt.client.FetchMetricCluster(cid, "")
if err != nil {
return circonusMetricCluster{}, err
}
mc.MetricCluster = *cmc
return mc, nil
}
func (mc *circonusMetricCluster) Create(ctxt *providerContext) error {
cmc, err := ctxt.client.CreateMetricCluster(&mc.MetricCluster)
if err != nil {
return err
}
mc.CID = cmc.CID
return nil
}
func (mc *circonusMetricCluster) Update(ctxt *providerContext) error {
_, err := ctxt.client.UpdateMetricCluster(&mc.MetricCluster)
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to update stream group %s: {{err}}", mc.CID), err)
}
return nil
}
func (mc *circonusMetricCluster) Validate() error {
if len(mc.Queries) < 1 {
return fmt.Errorf("there must be at least one stream group query present")
}
return nil
}

View File

@ -0,0 +1,19 @@
package circonus
import "testing"
func Test_MetricChecksum(t *testing.T) {
unit := "qty"
m := interfaceMap{
string(metricActiveAttr): true,
string(metricNameAttr): "asdf",
string(metricTagsAttr): tagsToState(apiToTags([]string{"foo", "bar"})),
string(metricTypeAttr): "json",
string(metricUnitAttr): &unit,
}
csum := metricChecksum(m)
if csum != 4250221491 {
t.Fatalf("Checksum mismatch")
}
}

View File

@ -0,0 +1,135 @@
package circonus
import (
"bytes"
"fmt"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
const (
defaultCirconus404ErrorString = "API response code 404:"
defaultCirconusAggregationWindow = "300s"
defaultCirconusAlertMinEscalateAfter = "300s"
defaultCirconusCheckPeriodMax = "300s"
defaultCirconusCheckPeriodMin = "30s"
defaultCirconusHTTPFormat = "json"
defaultCirconusHTTPMethod = "POST"
defaultCirconusSlackUsername = "Circonus"
defaultCirconusTimeoutMax = "300s"
defaultCirconusTimeoutMin = "0s"
maxSeverity = 5
minSeverity = 1
)
var providerDescription = map[string]string{
providerAPIURLAttr: "URL of the Circonus API",
providerAutoTagAttr: "Signals that the provider should automatically add a tag to all API calls denoting that the resource was created by Terraform",
providerKeyAttr: "API token used to authenticate with the Circonus API",
}
// Constants that want to be a constant but can't in Go
var (
validContactHTTPFormats = validStringValues{"json", "params"}
validContactHTTPMethods = validStringValues{"GET", "POST"}
)
type contactMethods string
// globalAutoTag controls whether or not the provider should automatically add a
// tag to each resource.
//
// NOTE(sean): This is done as a global variable because the diff suppress
// functions does not have access to the providerContext, only the key, old, and
// new values.
var globalAutoTag bool
type providerContext struct {
// Circonus API client
client *api.API
// autoTag, when true, automatically appends defaultCirconusTag
autoTag bool
// defaultTag make up the tag to be used when autoTag tags a tag.
defaultTag circonusTag
}
// Provider returns a terraform.ResourceProvider.
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
providerAPIURLAttr: {
Type: schema.TypeString,
Optional: true,
Default: "https://api.circonus.com/v2",
Description: providerDescription[providerAPIURLAttr],
},
providerAutoTagAttr: {
Type: schema.TypeBool,
Optional: true,
Default: defaultAutoTag,
Description: providerDescription[providerAutoTagAttr],
},
providerKeyAttr: {
Type: schema.TypeString,
Required: true,
Sensitive: true,
DefaultFunc: schema.EnvDefaultFunc("CIRCONUS_API_TOKEN", nil),
Description: providerDescription[providerKeyAttr],
},
},
DataSourcesMap: map[string]*schema.Resource{
"circonus_account": dataSourceCirconusAccount(),
"circonus_collector": dataSourceCirconusCollector(),
},
ResourcesMap: map[string]*schema.Resource{
"circonus_check": resourceCheck(),
"circonus_contact_group": resourceContactGroup(),
"circonus_graph": resourceGraph(),
"circonus_metric": resourceMetric(),
"circonus_metric_cluster": resourceMetricCluster(),
"circonus_rule_set": resourceRuleSet(),
},
ConfigureFunc: providerConfigure,
}
}
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
globalAutoTag = d.Get(providerAutoTagAttr).(bool)
config := &api.Config{
URL: d.Get(providerAPIURLAttr).(string),
TokenKey: d.Get(providerKeyAttr).(string),
TokenApp: tfAppName(),
}
client, err := api.NewAPI(config)
if err != nil {
return nil, errwrap.Wrapf("Error initializing Circonus: %s", err)
}
return &providerContext{
client: client,
autoTag: d.Get(providerAutoTagAttr).(bool),
defaultTag: defaultCirconusTag,
}, nil
}
func tfAppName() string {
const VersionPrerelease = terraform.VersionPrerelease
var versionString bytes.Buffer
fmt.Fprintf(&versionString, "Terraform v%s", terraform.Version)
if VersionPrerelease != "" {
fmt.Fprintf(&versionString, "-%s", VersionPrerelease)
}
return versionString.String()
}

View File

@ -0,0 +1,35 @@
package circonus
import (
"os"
"testing"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider
func init() {
testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{
"circonus": testAccProvider,
}
}
func TestProvider(t *testing.T) {
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestProvider_impl(t *testing.T) {
var _ terraform.ResourceProvider = Provider()
}
func testAccPreCheck(t *testing.T) {
if apiToken := os.Getenv("CIRCONUS_API_TOKEN"); apiToken == "" {
t.Fatal("CIRCONUS_API_TOKEN must be set for acceptance tests")
}
}

View File

@ -0,0 +1,607 @@
package circonus
/*
* Note to future readers: The `circonus_check` resource is actually a facade for
* the check_bundle call. check_bundle is an implementation detail that we mask
* over and expose just a "check" even though the "check" is actually a
* check_bundle.
*
* Style note: There are three directions that information flows:
*
* 1) Terraform Config file into API Objects. *Attr named objects are Config or
* Schema attribute names. In this file, all config constants should be
* named check*Attr.
*
* 2) API Objects into Statefile data. api*Attr named constants are parameters
* that originate from the API and need to be mapped into the provider's
* vernacular.
*/
import (
"fmt"
"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/schema"
)
const (
// circonus_check.* global resource attribute names
checkActiveAttr = "active"
checkCAQLAttr = "caql"
checkCloudWatchAttr = "cloudwatch"
checkCollectorAttr = "collector"
checkHTTPAttr = "http"
checkHTTPTrapAttr = "httptrap"
checkICMPPingAttr = "icmp_ping"
checkJSONAttr = "json"
checkMetricLimitAttr = "metric_limit"
checkMySQLAttr = "mysql"
checkNameAttr = "name"
checkNotesAttr = "notes"
checkPeriodAttr = "period"
checkPostgreSQLAttr = "postgresql"
checkMetricAttr = "metric"
checkTagsAttr = "tags"
checkTargetAttr = "target"
checkTCPAttr = "tcp"
checkTimeoutAttr = "timeout"
checkTypeAttr = "type"
// circonus_check.collector.* resource attribute names
checkCollectorIDAttr = "id"
// circonus_check.metric.* resource attribute names are aliased to
// circonus_metric.* resource attributes.
// circonus_check.metric.* resource attribute names
// metricIDAttr = "id"
// Out parameters for circonus_check
checkOutByCollectorAttr = "check_by_collector"
checkOutCheckUUIDsAttr = "uuids"
checkOutChecksAttr = "checks"
checkOutCreatedAttr = "created"
checkOutLastModifiedAttr = "last_modified"
checkOutLastModifiedByAttr = "last_modified_by"
checkOutReverseConnectURLsAttr = "reverse_connect_urls"
)
const (
// Circonus API constants from their API endpoints
apiCheckTypeCAQLAttr apiCheckType = "caql"
apiCheckTypeCloudWatchAttr apiCheckType = "cloudwatch"
apiCheckTypeHTTPAttr apiCheckType = "http"
apiCheckTypeHTTPTrapAttr apiCheckType = "httptrap"
apiCheckTypeICMPPingAttr apiCheckType = "ping_icmp"
apiCheckTypeJSONAttr apiCheckType = "json"
apiCheckTypeMySQLAttr apiCheckType = "mysql"
apiCheckTypePostgreSQLAttr apiCheckType = "postgres"
apiCheckTypeTCPAttr apiCheckType = "tcp"
)
var checkDescriptions = attrDescrs{
checkActiveAttr: "If the check is activate or disabled",
checkCAQLAttr: "CAQL check configuration",
checkCloudWatchAttr: "CloudWatch check configuration",
checkCollectorAttr: "The collector(s) that are responsible for gathering the metrics",
checkHTTPAttr: "HTTP check configuration",
checkHTTPTrapAttr: "HTTP Trap check configuration",
checkICMPPingAttr: "ICMP ping check configuration",
checkJSONAttr: "JSON check configuration",
checkMetricLimitAttr: `Setting a metric_limit will enable all (-1), disable (0), or allow up to the specified limit of metrics for this check ("N+", where N is a positive integer)`,
checkMySQLAttr: "MySQL check configuration",
checkNameAttr: "The name of the check bundle that will be displayed in the web interface",
checkNotesAttr: "Notes about this check bundle",
checkPeriodAttr: "The period between each time the check is made",
checkPostgreSQLAttr: "PostgreSQL check configuration",
checkMetricAttr: "Configuration for a stream of metrics",
checkTagsAttr: "A list of tags assigned to the check",
checkTargetAttr: "The target of the check (e.g. hostname, URL, IP, etc)",
checkTCPAttr: "TCP check configuration",
checkTimeoutAttr: "The length of time in seconds (and fractions of a second) before the check will timeout if no response is returned to the collector",
checkTypeAttr: "The check type",
checkOutChecksAttr: "",
checkOutByCollectorAttr: "",
checkOutCheckUUIDsAttr: "",
checkOutCreatedAttr: "",
checkOutLastModifiedAttr: "",
checkOutLastModifiedByAttr: "",
checkOutReverseConnectURLsAttr: "",
}
var checkCollectorDescriptions = attrDescrs{
checkCollectorIDAttr: "The ID of the collector",
}
var checkMetricDescriptions = metricDescriptions
func resourceCheck() *schema.Resource {
return &schema.Resource{
Create: checkCreate,
Read: checkRead,
Update: checkUpdate,
Delete: checkDelete,
Exists: checkExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: convertToHelperSchema(checkDescriptions, map[schemaAttr]*schema.Schema{
checkActiveAttr: &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
checkCAQLAttr: schemaCheckCAQL,
checkCloudWatchAttr: schemaCheckCloudWatch,
checkCollectorAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MinItems: 1,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkCollectorDescriptions, map[schemaAttr]*schema.Schema{
checkCollectorIDAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRegexp(checkCollectorIDAttr, config.BrokerCIDRegex),
},
}),
},
},
checkHTTPAttr: schemaCheckHTTP,
checkHTTPTrapAttr: schemaCheckHTTPTrap,
checkJSONAttr: schemaCheckJSON,
checkICMPPingAttr: schemaCheckICMPPing,
checkMetricLimitAttr: &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Computed: true,
ValidateFunc: validateFuncs(
validateIntMin(checkMetricLimitAttr, -1),
),
},
checkMySQLAttr: schemaCheckMySQL,
checkNameAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
checkNotesAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
StateFunc: suppressWhitespace,
},
checkPeriodAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
StateFunc: normalizeTimeDurationStringToSeconds,
ValidateFunc: validateFuncs(
validateDurationMin(checkPeriodAttr, defaultCirconusCheckPeriodMin),
validateDurationMax(checkPeriodAttr, defaultCirconusCheckPeriodMax),
),
},
checkPostgreSQLAttr: schemaCheckPostgreSQL,
checkMetricAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Set: checkMetricChecksum,
MinItems: 1,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkMetricDescriptions, map[schemaAttr]*schema.Schema{
metricActiveAttr: &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
metricNameAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRegexp(metricNameAttr, `[\S]+`),
},
metricTagsAttr: tagMakeConfigSchema(metricTagsAttr),
metricTypeAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateMetricType,
},
metricUnitAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: metricUnit,
ValidateFunc: validateRegexp(metricUnitAttr, metricUnitRegexp),
},
}),
},
},
checkTagsAttr: tagMakeConfigSchema(checkTagsAttr),
checkTargetAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validateRegexp(checkTagsAttr, `.+`),
},
checkTCPAttr: schemaCheckTCP,
checkTimeoutAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
StateFunc: normalizeTimeDurationStringToSeconds,
ValidateFunc: validateFuncs(
validateDurationMin(checkTimeoutAttr, defaultCirconusTimeoutMin),
validateDurationMax(checkTimeoutAttr, defaultCirconusTimeoutMax),
),
},
checkTypeAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
ValidateFunc: validateCheckType,
},
// Out parameters
checkOutByCollectorAttr: &schema.Schema{
Type: schema.TypeMap,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
checkOutCheckUUIDsAttr: &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
checkOutChecksAttr: &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
checkOutCreatedAttr: &schema.Schema{
Type: schema.TypeInt,
Computed: true,
},
checkOutLastModifiedAttr: &schema.Schema{
Type: schema.TypeInt,
Computed: true,
},
checkOutLastModifiedByAttr: &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
checkOutReverseConnectURLsAttr: &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
}),
}
}
func checkCreate(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
c := newCheck()
if err := c.ParseConfig(d); err != nil {
return errwrap.Wrapf("error parsing check schema during create: {{err}}", err)
}
if err := c.Create(ctxt); err != nil {
return errwrap.Wrapf("error creating check: {{err}}", err)
}
d.SetId(c.CID)
return checkRead(d, meta)
}
func checkExists(d *schema.ResourceData, meta interface{}) (bool, error) {
ctxt := meta.(*providerContext)
cid := d.Id()
cb, err := ctxt.client.FetchCheckBundle(api.CIDType(&cid))
if err != nil {
return false, err
}
if cb.CID == "" {
return false, nil
}
return true, nil
}
// checkRead pulls data out of the CheckBundle object and stores it into the
// appropriate place in the statefile.
func checkRead(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
cid := d.Id()
c, err := loadCheck(ctxt, api.CIDType(&cid))
if err != nil {
return err
}
d.SetId(c.CID)
// Global circonus_check attributes are saved first, followed by the check
// type specific attributes handled below in their respective checkRead*().
checkIDsByCollector := make(map[string]interface{}, len(c.Checks))
for i, b := range c.Brokers {
checkIDsByCollector[b] = c.Checks[i]
}
metrics := schema.NewSet(checkMetricChecksum, nil)
for _, m := range c.Metrics {
metricAttrs := map[string]interface{}{
string(metricActiveAttr): metricAPIStatusToBool(m.Status),
string(metricNameAttr): m.Name,
string(metricTagsAttr): tagsToState(apiToTags(m.Tags)),
string(metricTypeAttr): m.Type,
string(metricUnitAttr): indirect(m.Units),
}
metrics.Add(metricAttrs)
}
// Write the global circonus_check parameters followed by the check
// type-specific parameters.
d.Set(checkActiveAttr, checkAPIStatusToBool(c.Status))
if err := d.Set(checkCollectorAttr, stringListToSet(c.Brokers, checkCollectorIDAttr)); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkCollectorAttr), err)
}
d.Set(checkMetricLimitAttr, c.MetricLimit)
d.Set(checkNameAttr, c.DisplayName)
d.Set(checkNotesAttr, c.Notes)
d.Set(checkPeriodAttr, fmt.Sprintf("%ds", c.Period))
if err := d.Set(checkMetricAttr, metrics); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkMetricAttr), err)
}
if err := d.Set(checkTagsAttr, c.Tags); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkTagsAttr), err)
}
d.Set(checkTargetAttr, c.Target)
{
t, _ := time.ParseDuration(fmt.Sprintf("%fs", c.Timeout))
d.Set(checkTimeoutAttr, t.String())
}
d.Set(checkTypeAttr, c.Type)
// Last step: parse a check_bundle's config into the statefile.
if err := parseCheckTypeConfig(&c, d); err != nil {
return errwrap.Wrapf("Unable to parse check config: {{err}}", err)
}
// Out parameters
if err := d.Set(checkOutByCollectorAttr, checkIDsByCollector); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkOutByCollectorAttr), err)
}
if err := d.Set(checkOutCheckUUIDsAttr, c.CheckUUIDs); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkOutCheckUUIDsAttr), err)
}
if err := d.Set(checkOutChecksAttr, c.Checks); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkOutChecksAttr), err)
}
d.Set(checkOutCreatedAttr, c.Created)
d.Set(checkOutLastModifiedAttr, c.LastModified)
d.Set(checkOutLastModifiedByAttr, c.LastModifedBy)
if err := d.Set(checkOutReverseConnectURLsAttr, c.ReverseConnectURLs); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkOutReverseConnectURLsAttr), err)
}
return nil
}
func checkUpdate(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
c := newCheck()
if err := c.ParseConfig(d); err != nil {
return err
}
c.CID = d.Id()
if err := c.Update(ctxt); err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to update check %q: {{err}}", d.Id()), err)
}
return checkRead(d, meta)
}
func checkDelete(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
if _, err := ctxt.client.Delete(d.Id()); err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to delete check %q: {{err}}", d.Id()), err)
}
d.SetId("")
return nil
}
func checkMetricChecksum(v interface{}) int {
m := v.(map[string]interface{})
csum := metricChecksum(m)
return csum
}
// ParseConfig reads Terraform config data and stores the information into a
// Circonus CheckBundle object.
func (c *circonusCheck) ParseConfig(d *schema.ResourceData) error {
if v, found := d.GetOk(checkActiveAttr); found {
c.Status = checkActiveToAPIStatus(v.(bool))
}
if v, found := d.GetOk(checkCollectorAttr); found {
l := v.(*schema.Set).List()
c.Brokers = make([]string, 0, len(l))
for _, mapRaw := range l {
mapAttrs := mapRaw.(map[string]interface{})
if mv, mapFound := mapAttrs[checkCollectorIDAttr]; mapFound {
c.Brokers = append(c.Brokers, mv.(string))
}
}
}
if v, found := d.GetOk(checkMetricLimitAttr); found {
c.MetricLimit = v.(int)
}
if v, found := d.GetOk(checkNameAttr); found {
c.DisplayName = v.(string)
}
if v, found := d.GetOk(checkNotesAttr); found {
s := v.(string)
c.Notes = &s
}
if v, found := d.GetOk(checkPeriodAttr); found {
d, err := time.ParseDuration(v.(string))
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to parse %q as a duration: {{err}}", checkPeriodAttr), err)
}
c.Period = uint(d.Seconds())
}
if v, found := d.GetOk(checkMetricAttr); found {
metricList := v.(*schema.Set).List()
c.Metrics = make([]api.CheckBundleMetric, 0, len(metricList))
for _, metricListRaw := range metricList {
metricAttrs := metricListRaw.(map[string]interface{})
var id string
if av, found := metricAttrs[metricIDAttr]; found {
id = av.(string)
} else {
var err error
id, err = newMetricID()
if err != nil {
return errwrap.Wrapf("unable to create a new metric ID: {{err}}", err)
}
}
m := newMetric()
if err := m.ParseConfigMap(id, metricAttrs); err != nil {
return errwrap.Wrapf("unable to parse config: {{err}}", err)
}
c.Metrics = append(c.Metrics, m.CheckBundleMetric)
}
}
if v, found := d.GetOk(checkTagsAttr); found {
c.Tags = derefStringList(flattenSet(v.(*schema.Set)))
}
if v, found := d.GetOk(checkTargetAttr); found {
c.Target = v.(string)
}
if v, found := d.GetOk(checkTimeoutAttr); found {
d, err := time.ParseDuration(v.(string))
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to parse %q as a duration: {{err}}", checkTimeoutAttr), err)
}
t := float32(d.Seconds())
c.Timeout = t
}
// Last step: parse the individual check types
if err := checkConfigToAPI(c, d); err != nil {
return errwrap.Wrapf("unable to parse check type: {{err}}", err)
}
if err := c.Fixup(); err != nil {
return err
}
if err := c.Validate(); err != nil {
return err
}
return nil
}
// checkConfigToAPI parses the Terraform config into the respective per-check
// type api.Config attributes.
func checkConfigToAPI(c *circonusCheck, d *schema.ResourceData) error {
checkTypeParseMap := map[string]func(*circonusCheck, interfaceList) error{
checkCAQLAttr: checkConfigToAPICAQL,
checkCloudWatchAttr: checkConfigToAPICloudWatch,
checkHTTPAttr: checkConfigToAPIHTTP,
checkHTTPTrapAttr: checkConfigToAPIHTTPTrap,
checkICMPPingAttr: checkConfigToAPIICMPPing,
checkJSONAttr: checkConfigToAPIJSON,
checkMySQLAttr: checkConfigToAPIMySQL,
checkPostgreSQLAttr: checkConfigToAPIPostgreSQL,
checkTCPAttr: checkConfigToAPITCP,
}
for checkType, fn := range checkTypeParseMap {
if listRaw, found := d.GetOk(checkType); found {
if err := fn(c, listRaw.(*schema.Set).List()); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to parse type %q: {{err}}", string(checkType)), err)
}
}
}
return nil
}
// parseCheckTypeConfig parses an API Config object and stores the result in the
// statefile.
func parseCheckTypeConfig(c *circonusCheck, d *schema.ResourceData) error {
checkTypeConfigHandlers := map[apiCheckType]func(*circonusCheck, *schema.ResourceData) error{
apiCheckTypeCAQLAttr: checkAPIToStateCAQL,
apiCheckTypeCloudWatchAttr: checkAPIToStateCloudWatch,
apiCheckTypeHTTPAttr: checkAPIToStateHTTP,
apiCheckTypeHTTPTrapAttr: checkAPIToStateHTTPTrap,
apiCheckTypeICMPPingAttr: checkAPIToStateICMPPing,
apiCheckTypeJSONAttr: checkAPIToStateJSON,
apiCheckTypeMySQLAttr: checkAPIToStateMySQL,
apiCheckTypePostgreSQLAttr: checkAPIToStatePostgreSQL,
apiCheckTypeTCPAttr: checkAPIToStateTCP,
}
var checkType apiCheckType = apiCheckType(c.Type)
fn, ok := checkTypeConfigHandlers[checkType]
if !ok {
return fmt.Errorf("check type %q not supported", c.Type)
}
if err := fn(c, d); err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to parse the API config for %q: {{err}}", c.Type), err)
}
return nil
}

View File

@ -0,0 +1,89 @@
package circonus
import (
"bytes"
"fmt"
"strings"
"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_check.caql.* resource attribute names
checkCAQLQueryAttr = "query"
)
var checkCAQLDescriptions = attrDescrs{
checkCAQLQueryAttr: "The query definition",
}
var schemaCheckCAQL = &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
MinItems: 1,
Set: hashCheckCAQL,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkCAQLDescriptions, map[schemaAttr]*schema.Schema{
checkCAQLQueryAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRegexp(checkCAQLQueryAttr, `.+`),
},
}),
},
}
// checkAPIToStateCAQL reads the Config data out of circonusCheck.CheckBundle
// into the statefile.
func checkAPIToStateCAQL(c *circonusCheck, d *schema.ResourceData) error {
caqlConfig := make(map[string]interface{}, len(c.Config))
caqlConfig[string(checkCAQLQueryAttr)] = c.Config[config.Query]
if err := d.Set(checkCAQLAttr, schema.NewSet(hashCheckCAQL, []interface{}{caqlConfig})); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkCAQLAttr), err)
}
return nil
}
// hashCheckCAQL creates a stable hash of the normalized values
func hashCheckCAQL(v interface{}) int {
m := v.(map[string]interface{})
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
writeString := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok && v.(string) != "" {
fmt.Fprint(b, strings.TrimSpace(v.(string)))
}
}
// Order writes to the buffer using lexically sorted list for easy visual
// reconciliation with other lists.
writeString(checkCAQLQueryAttr)
s := b.String()
return hashcode.String(s)
}
func checkConfigToAPICAQL(c *circonusCheck, l interfaceList) error {
c.Type = string(apiCheckTypeCAQL)
c.Target = defaultCheckCAQLTarget
// Iterate over all `icmp_ping` attributes, even though we have a max of 1 in
// the schema.
for _, mapRaw := range l {
caqlConfig := newInterfaceMap(mapRaw)
if v, found := caqlConfig[checkCAQLQueryAttr]; found {
c.Config[config.Query] = v.(string)
}
}
return nil
}

View File

@ -0,0 +1,74 @@
package circonus
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccCirconusCheckCAQL_basic(t *testing.T) {
checkName := fmt.Sprintf("Consul's Go GC latency (Merged Histogram) - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckCAQLConfigFmt, checkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "active", "true"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "collector.36214388.id", "/broker/1490"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "caql.#", "1"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "caql.4060628048.query", `search:metric:histogram("*consul*runtime`+"`"+`gc_pause_ns* (active:1)") | histogram:merge() | histogram:percentile(99)`),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "metric.#", "1"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "tags.3728194417", "app:consul"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "tags.3480593708", "source:goruntime"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "target", "q._caql"),
resource.TestCheckResourceAttr("circonus_check.go_gc_latency", "type", "caql"),
),
},
},
})
}
const testAccCirconusCheckCAQLConfigFmt = `
variable "test_tags" {
type = "list"
default = [ "app:consul", "author:terraform", "lifecycle:unittest", "source:goruntime" ]
}
resource "circonus_check" "go_gc_latency" {
active = true
name = "%s"
period = "60s"
collector {
id = "/broker/1490"
}
caql {
query = <<EOF
search:metric:histogram("*consul*runtime` + "`" + `gc_pause_ns* (active:1)") | histogram:merge() | histogram:percentile(99)
EOF
}
metric {
name = "output[1]"
tags = [ "${var.test_tags}" ]
type = "histogram"
unit = "seconds"
}
tags = [ "${var.test_tags}" ]
}
`

View File

@ -0,0 +1,260 @@
package circonus
import (
"bytes"
"fmt"
"log"
"sort"
"strings"
"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_check.cloudwatch.* resource attribute names
checkCloudWatchAPIKeyAttr = "api_key"
checkCloudWatchAPISecretAttr = "api_secret"
checkCloudWatchDimmensionsAttr = "dimmensions"
checkCloudWatchMetricAttr = "metric"
checkCloudWatchNamespaceAttr = "namespace"
checkCloudWatchURLAttr = "url"
checkCloudWatchVersionAttr = "version"
)
var checkCloudWatchDescriptions = attrDescrs{
checkCloudWatchAPIKeyAttr: "The AWS API Key",
checkCloudWatchAPISecretAttr: "The AWS API Secret",
checkCloudWatchDimmensionsAttr: "The dimensions to query for the metric",
checkCloudWatchMetricAttr: "One or more CloudWatch Metric attributes",
checkCloudWatchNamespaceAttr: "The namespace to pull telemetry from",
checkCloudWatchURLAttr: "The URL including schema and hostname for the Cloudwatch monitoring server. This value will be used to specify the region - for example, to pull from us-east-1, the URL would be https://monitoring.us-east-1.amazonaws.com.",
checkCloudWatchVersionAttr: "The version of the Cloudwatch API to use.",
}
var schemaCheckCloudWatch = &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
MinItems: 1,
Set: hashCheckCloudWatch,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkCloudWatchDescriptions, map[schemaAttr]*schema.Schema{
checkCloudWatchAPIKeyAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
Sensitive: true,
ValidateFunc: validateRegexp(checkCloudWatchAPIKeyAttr, `[\S]+`),
DefaultFunc: schema.EnvDefaultFunc("AWS_ACCESS_KEY_ID", ""),
},
checkCloudWatchAPISecretAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
Sensitive: true,
ValidateFunc: validateRegexp(checkCloudWatchAPISecretAttr, `[\S]+`),
DefaultFunc: schema.EnvDefaultFunc("AWS_SECRET_ACCESS_KEY", ""),
},
checkCloudWatchDimmensionsAttr: &schema.Schema{
Type: schema.TypeMap,
Required: true,
Elem: schema.TypeString,
ValidateFunc: validateCheckCloudWatchDimmensions,
},
checkCloudWatchMetricAttr: &schema.Schema{
Type: schema.TypeSet,
Required: true,
MinItems: 1,
Set: schema.HashString,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateRegexp(checkCloudWatchMetricAttr, `^([\S]+)$`),
},
},
checkCloudWatchNamespaceAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRegexp(checkCloudWatchNamespaceAttr, `.+`),
},
checkCloudWatchURLAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateHTTPURL(checkCloudWatchURLAttr, urlIsAbs),
},
checkCloudWatchVersionAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCheckCloudWatchVersion,
ValidateFunc: validateRegexp(checkCloudWatchVersionAttr, `^[\d]{4}-[\d]{2}-[\d]{2}$`),
},
}),
},
}
// checkAPIToStateCloudWatch reads the Config data out of circonusCheck.CheckBundle into the
// statefile.
func checkAPIToStateCloudWatch(c *circonusCheck, d *schema.ResourceData) error {
cloudwatchConfig := make(map[string]interface{}, len(c.Config))
// swamp is a sanity check: it must be empty by the time this method returns
swamp := make(map[config.Key]string, len(c.Config))
for k, v := range c.Config {
swamp[k] = v
}
saveStringConfigToState := func(apiKey config.Key, attrName schemaAttr) {
if v, ok := c.Config[apiKey]; ok {
cloudwatchConfig[string(attrName)] = v
}
delete(swamp, apiKey)
}
saveStringConfigToState(config.APIKey, checkCloudWatchAPIKeyAttr)
saveStringConfigToState(config.APISecret, checkCloudWatchAPISecretAttr)
dimmensions := make(map[string]interface{}, len(c.Config))
dimmensionPrefixLen := len(config.DimPrefix)
for k, v := range c.Config {
if len(k) <= dimmensionPrefixLen {
continue
}
if strings.Compare(string(k[:dimmensionPrefixLen]), string(config.DimPrefix)) == 0 {
key := k[dimmensionPrefixLen:]
dimmensions[string(key)] = v
}
delete(swamp, k)
}
cloudwatchConfig[string(checkCloudWatchDimmensionsAttr)] = dimmensions
metricSet := schema.NewSet(schema.HashString, nil)
metricList := strings.Split(c.Config[config.CloudwatchMetrics], ",")
for _, m := range metricList {
metricSet.Add(m)
}
cloudwatchConfig[string(checkCloudWatchMetricAttr)] = metricSet
saveStringConfigToState(config.Namespace, checkCloudWatchNamespaceAttr)
saveStringConfigToState(config.URL, checkCloudWatchURLAttr)
saveStringConfigToState(config.Version, checkCloudWatchVersionAttr)
whitelistedConfigKeys := map[config.Key]struct{}{
config.ReverseSecretKey: struct{}{},
config.SubmissionURL: struct{}{},
}
for k := range swamp {
if _, ok := whitelistedConfigKeys[k]; ok {
delete(c.Config, k)
}
if _, ok := whitelistedConfigKeys[k]; !ok {
log.Printf("[ERROR]: PROVIDER BUG: API Config not empty: %#v", swamp)
}
}
if err := d.Set(checkCloudWatchAttr, schema.NewSet(hashCheckCloudWatch, []interface{}{cloudwatchConfig})); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkCloudWatchAttr), err)
}
return nil
}
// hashCheckCloudWatch creates a stable hash of the normalized values
func hashCheckCloudWatch(v interface{}) int {
m := v.(map[string]interface{})
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
writeString := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok && v.(string) != "" {
fmt.Fprint(b, strings.TrimSpace(v.(string)))
}
}
// Order writes to the buffer using lexically sorted list for easy visual
// reconciliation with other lists.
writeString(checkCloudWatchAPIKeyAttr)
writeString(checkCloudWatchAPISecretAttr)
if dimmensionsRaw, ok := m[string(checkCloudWatchDimmensionsAttr)]; ok {
dimmensionMap := dimmensionsRaw.(map[string]interface{})
dimmensions := make([]string, 0, len(dimmensionMap))
for k := range dimmensionMap {
dimmensions = append(dimmensions, k)
}
sort.Strings(dimmensions)
for i := range dimmensions {
fmt.Fprint(b, dimmensions[i])
}
}
if metricsRaw, ok := m[string(checkCloudWatchMetricAttr)]; ok {
metricListRaw := flattenSet(metricsRaw.(*schema.Set))
for i := range metricListRaw {
if metricListRaw[i] == nil {
continue
}
fmt.Fprint(b, *metricListRaw[i])
}
}
writeString(checkCloudWatchNamespaceAttr)
writeString(checkCloudWatchURLAttr)
writeString(checkCloudWatchVersionAttr)
s := b.String()
return hashcode.String(s)
}
func checkConfigToAPICloudWatch(c *circonusCheck, l interfaceList) error {
c.Type = string(apiCheckTypeCloudWatchAttr)
// Iterate over all `cloudwatch` attributes, even though we have a max of 1 in the
// schema.
for _, mapRaw := range l {
cloudwatchConfig := newInterfaceMap(mapRaw)
if v, found := cloudwatchConfig[checkCloudWatchAPIKeyAttr]; found {
c.Config[config.APIKey] = v.(string)
}
if v, found := cloudwatchConfig[checkCloudWatchAPISecretAttr]; found {
c.Config[config.APISecret] = v.(string)
}
if dimmensions := cloudwatchConfig.CollectMap(checkCloudWatchDimmensionsAttr); dimmensions != nil {
for k, v := range dimmensions {
dimKey := config.DimPrefix + config.Key(k)
c.Config[dimKey] = v
}
}
if v, found := cloudwatchConfig[checkCloudWatchMetricAttr]; found {
metricsRaw := v.(*schema.Set).List()
metrics := make([]string, 0, len(metricsRaw))
for _, m := range metricsRaw {
metrics = append(metrics, m.(string))
}
sort.Strings(metrics)
c.Config[config.CloudwatchMetrics] = strings.Join(metrics, ",")
}
if v, found := cloudwatchConfig[checkCloudWatchNamespaceAttr]; found {
c.Config[config.Namespace] = v.(string)
}
if v, found := cloudwatchConfig[checkCloudWatchURLAttr]; found {
c.Config[config.URL] = v.(string)
}
if v, found := cloudwatchConfig[checkCloudWatchVersionAttr]; found {
c.Config[config.Version] = v.(string)
}
}
return nil
}

View File

@ -0,0 +1,390 @@
package circonus
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccCirconusCheckCloudWatch_basic(t *testing.T) {
checkName := fmt.Sprintf("Terraform test: RDS Metrics via CloudWatch - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckCloudWatchConfigFmt, checkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "collector.2388330941.id", "/broker/1"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.#", "1"),
// resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.api_key", ""),
// resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.api_secret", ""),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.dimmensions.%", "1"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.dimmensions.DBInstanceIdentifier", "atlas-production"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.#", "17"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.990896688", "CPUUtilization"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.3895259375", "DatabaseConnections"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.1328149445", "DiskQueueDepth"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.4218650584", "FreeStorageSpace"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.1835248983", "FreeableMemory"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.2757008135", "MaximumUsedTransactionIDs"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.915415866", "NetworkReceiveThroughput"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.1852047735", "NetworkTransmitThroughput"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.3518416306", "ReadIOPS"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.114013313", "ReadLatency"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.1284099341", "ReadThroughput"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.4205329773", "SwapUsage"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.3550163941", "TransactionLogsDiskUsage"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.2231806695", "TransactionLogsGeneration"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.335777904", "WriteIOPS"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.3894876280", "WriteLatency"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.metric.1569904650", "WriteThroughput"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.namespace", "AWS/RDS"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.version", "2010-08-01"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "cloudwatch.2270818665.url", "https://monitoring.us-east-1.amazonaws.com"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "notes", "Collect all the things exposed"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.#", "17"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.11714944.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.11714944.name", "ReadLatency"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.11714944.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.11714944.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.11714944.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.11714944.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.11714944.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.11714944.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.11714944.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1436709022.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1436709022.name", "TransactionLogsGeneration"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1436709022.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1436709022.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1436709022.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1436709022.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1436709022.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1436709022.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1436709022.unit", ""),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1444027024.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1444027024.name", "WriteIOPS"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1444027024.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1444027024.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1444027024.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1444027024.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1444027024.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1444027024.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1444027024.unit", "iops"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1604797265.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1604797265.name", "FreeStorageSpace"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1604797265.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1604797265.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1604797265.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1604797265.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1604797265.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1604797265.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1604797265.unit", ""),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1605952596.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1605952596.name", "WriteLatency"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1605952596.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1605952596.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1605952596.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1605952596.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1605952596.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1605952596.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1605952596.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1714840347.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1714840347.name", "DatabaseConnections"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1714840347.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1714840347.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1714840347.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1714840347.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1714840347.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1714840347.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.1714840347.unit", "connections"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2132240407.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2132240407.name", "FreeableMemory"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2132240407.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2132240407.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2132240407.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2132240407.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2132240407.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2132240407.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2132240407.unit", "bytes"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2395338478.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2395338478.name", "MaximumUsedTransactionIDs"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2395338478.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2395338478.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2395338478.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2395338478.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2395338478.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2395338478.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2395338478.unit", ""),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2968437811.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2968437811.name", "ReadThroughput"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2968437811.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2968437811.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2968437811.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2968437811.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2968437811.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2968437811.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.2968437811.unit", "bytes"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3023676211.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3023676211.name", "ReadIOPS"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3023676211.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3023676211.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3023676211.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3023676211.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3023676211.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3023676211.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3023676211.unit", "iops"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3053289991.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3053289991.name", "NetworkReceiveThroughput"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3053289991.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3053289991.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3053289991.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3053289991.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3053289991.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3053289991.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3053289991.unit", "bytes"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3187210440.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3187210440.name", "TransactionLogsDiskUsage"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3187210440.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3187210440.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3187210440.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3187210440.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3187210440.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3187210440.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3187210440.unit", "bytes"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3202842729.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3202842729.name", "CPUUtilization"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3202842729.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3202842729.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3202842729.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3202842729.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3202842729.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3202842729.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3202842729.unit", "%"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3527192726.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3527192726.name", "SwapUsage"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3527192726.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3527192726.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3527192726.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3527192726.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3527192726.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3527192726.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3527192726.unit", "bytes"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3740424181.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3740424181.name", "NetworkTransmitThroughput"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3740424181.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3740424181.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3740424181.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3740424181.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3740424181.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3740424181.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.3740424181.unit", "bytes"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.53704089.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.53704089.name", "DiskQueueDepth"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.53704089.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.53704089.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.53704089.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.53704089.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.53704089.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.53704089.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.53704089.unit", ""),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.823122139.active", "true"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.823122139.name", "WriteThroughput"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.823122139.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.823122139.tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.823122139.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.823122139.tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.823122139.tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.823122139.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "metric.823122139.unit", "bytes"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "tags.2964981562", "app:postgresql"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "tags.1313458811", "app:rds"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "tags.4259413593", "source:cloudwatch"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "target", "atlas-production.us-east-1.rds._aws"),
resource.TestCheckResourceAttr("circonus_check.rds_metrics", "type", "cloudwatch"),
),
},
},
})
}
const testAccCirconusCheckCloudWatchConfigFmt = `
variable "cloudwatch_rds_tags" {
type = "list"
default = [
"app:postgresql",
"app:rds",
"lifecycle:unittests",
"source:cloudwatch",
]
}
resource "circonus_check" "rds_metrics" {
active = true
name = "%s"
notes = "Collect all the things exposed"
period = "60s"
collector {
id = "/broker/1"
}
cloudwatch {
dimmensions = {
DBInstanceIdentifier = "atlas-production",
}
metric = [
"CPUUtilization",
"DatabaseConnections",
"DiskQueueDepth",
"FreeStorageSpace",
"FreeableMemory",
"MaximumUsedTransactionIDs",
"NetworkReceiveThroughput",
"NetworkTransmitThroughput",
"ReadIOPS",
"ReadLatency",
"ReadThroughput",
"SwapUsage",
"TransactionLogsDiskUsage",
"TransactionLogsGeneration",
"WriteIOPS",
"WriteLatency",
"WriteThroughput",
]
namespace = "AWS/RDS"
url = "https://monitoring.us-east-1.amazonaws.com"
}
metric {
name = "CPUUtilization"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "%%"
}
metric {
name = "DatabaseConnections"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "connections"
}
metric {
name = "DiskQueueDepth"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
}
metric {
name = "FreeStorageSpace"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
}
metric {
name = "FreeableMemory"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "bytes"
}
metric {
name = "MaximumUsedTransactionIDs"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
}
metric {
name = "NetworkReceiveThroughput"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "bytes"
}
metric {
name = "NetworkTransmitThroughput"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "bytes"
}
metric {
name = "ReadIOPS"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "iops"
}
metric {
name = "ReadLatency"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "ReadThroughput"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "bytes"
}
metric {
name = "SwapUsage"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "bytes"
}
metric {
name = "TransactionLogsDiskUsage"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "bytes"
}
metric {
name = "TransactionLogsGeneration"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
}
metric {
name = "WriteIOPS"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "iops"
}
metric {
name = "WriteLatency"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "WriteThroughput"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "bytes"
}
tags = [ "${var.cloudwatch_rds_tags}" ]
}
`

View File

@ -0,0 +1,383 @@
package circonus
import (
"bytes"
"fmt"
"log"
"net/url"
"sort"
"strconv"
"strings"
"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_check.http.* resource attribute names
checkHTTPAuthMethodAttr = "auth_method"
checkHTTPAuthPasswordAttr = "auth_password"
checkHTTPAuthUserAttr = "auth_user"
checkHTTPBodyRegexpAttr = "body_regexp"
checkHTTPCAChainAttr = "ca_chain"
checkHTTPCertFileAttr = "certificate_file"
checkHTTPCiphersAttr = "ciphers"
checkHTTPCodeRegexpAttr = "code"
checkHTTPExtractAttr = "extract"
checkHTTPHeadersAttr = "headers"
checkHTTPKeyFileAttr = "key_file"
checkHTTPMethodAttr = "method"
checkHTTPPayloadAttr = "payload"
checkHTTPReadLimitAttr = "read_limit"
checkHTTPURLAttr = "url"
checkHTTPVersionAttr = "version"
)
var checkHTTPDescriptions = attrDescrs{
checkHTTPAuthMethodAttr: "The HTTP Authentication method",
checkHTTPAuthPasswordAttr: "The HTTP Authentication user password",
checkHTTPAuthUserAttr: "The HTTP Authentication user name",
checkHTTPBodyRegexpAttr: `This regular expression is matched against the body of the response. If a match is not found, the check will be marked as "bad.`,
checkHTTPCAChainAttr: "A path to a file containing all the certificate authorities that should be loaded to validate the remote certificate (for TLS checks)",
checkHTTPCodeRegexpAttr: `The HTTP code that is expected. If the code received does not match this regular expression, the check is marked as "bad."`,
checkHTTPCiphersAttr: "A list of ciphers to be used in the TLS protocol (for HTTPS checks)",
checkHTTPCertFileAttr: "A path to a file containing the client certificate that will be presented to the remote server (for TLS-enabled checks)",
checkHTTPExtractAttr: "This regular expression is matched against the body of the response globally. The first capturing match is the key and the second capturing match is the value. Each key/value extracted is registered as a metric for the check.",
checkHTTPHeadersAttr: "Map of HTTP Headers to send along with HTTP Requests",
checkHTTPKeyFileAttr: "A path to a file containing key to be used in conjunction with the cilent certificate (for TLS checks)",
checkHTTPMethodAttr: "The HTTP method to use",
checkHTTPPayloadAttr: "The information transferred as the payload of an HTTP request",
checkHTTPReadLimitAttr: "Sets an approximate limit on the data read (0 means no limit)",
checkHTTPURLAttr: "The URL to use as the target of the check",
checkHTTPVersionAttr: "Sets the HTTP version for the check to use",
}
var schemaCheckHTTP = &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
MinItems: 1,
Set: hashCheckHTTP,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkHTTPDescriptions, map[schemaAttr]*schema.Schema{
checkHTTPAuthMethodAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkHTTPAuthMethodAttr, `^(?:Basic|Digest|Auto)$`),
},
checkHTTPAuthPasswordAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ValidateFunc: validateRegexp(checkHTTPAuthPasswordAttr, `^.*`),
},
checkHTTPAuthUserAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkHTTPAuthUserAttr, `[^:]+`),
},
checkHTTPBodyRegexpAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkHTTPBodyRegexpAttr, `.+`),
},
checkHTTPCAChainAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkHTTPCAChainAttr, `.+`),
},
checkHTTPCertFileAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkHTTPCertFileAttr, `.+`),
},
checkHTTPCiphersAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkHTTPCiphersAttr, `.+`),
},
checkHTTPCodeRegexpAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCheckHTTPCodeRegexp,
ValidateFunc: validateRegexp(checkHTTPCodeRegexpAttr, `.+`),
},
checkHTTPExtractAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkHTTPExtractAttr, `.+`),
},
checkHTTPHeadersAttr: &schema.Schema{
Type: schema.TypeMap,
Elem: schema.TypeString,
Optional: true,
ValidateFunc: validateHTTPHeaders,
},
checkHTTPKeyFileAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkHTTPKeyFileAttr, `.+`),
},
checkHTTPMethodAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCheckHTTPMethod,
ValidateFunc: validateRegexp(checkHTTPMethodAttr, `\S+`),
},
checkHTTPPayloadAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkHTTPPayloadAttr, `\S+`),
},
checkHTTPReadLimitAttr: &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validateFuncs(
validateIntMin(checkHTTPReadLimitAttr, 0),
),
},
checkHTTPURLAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateFuncs(
validateHTTPURL(checkHTTPURLAttr, urlIsAbs),
),
},
checkHTTPVersionAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCheckHTTPVersion,
ValidateFunc: validateStringIn(checkHTTPVersionAttr, supportedHTTPVersions),
},
}),
},
}
// checkAPIToStateHTTP reads the Config data out of circonusCheck.CheckBundle into the
// statefile.
func checkAPIToStateHTTP(c *circonusCheck, d *schema.ResourceData) error {
httpConfig := make(map[string]interface{}, len(c.Config))
// swamp is a sanity check: it must be empty by the time this method returns
swamp := make(map[config.Key]string, len(c.Config))
for k, v := range c.Config {
swamp[k] = v
}
saveStringConfigToState := func(apiKey config.Key, attrName schemaAttr) {
if v, ok := c.Config[apiKey]; ok {
httpConfig[string(attrName)] = v
}
delete(swamp, apiKey)
}
saveIntConfigToState := func(apiKey config.Key, attrName schemaAttr) {
if v, ok := c.Config[apiKey]; ok {
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
log.Printf("[ERROR]: Unable to convert %s to an integer: %v", apiKey, err)
return
}
httpConfig[string(attrName)] = int(i)
}
delete(swamp, apiKey)
}
saveStringConfigToState(config.AuthMethod, checkHTTPAuthMethodAttr)
saveStringConfigToState(config.AuthPassword, checkHTTPAuthPasswordAttr)
saveStringConfigToState(config.AuthUser, checkHTTPAuthUserAttr)
saveStringConfigToState(config.Body, checkHTTPBodyRegexpAttr)
saveStringConfigToState(config.CAChain, checkHTTPCAChainAttr)
saveStringConfigToState(config.CertFile, checkHTTPCertFileAttr)
saveStringConfigToState(config.Ciphers, checkHTTPCiphersAttr)
saveStringConfigToState(config.Code, checkHTTPCodeRegexpAttr)
saveStringConfigToState(config.Extract, checkHTTPExtractAttr)
headers := make(map[string]interface{}, len(c.Config))
headerPrefixLen := len(config.HeaderPrefix)
for k, v := range c.Config {
if len(k) <= headerPrefixLen {
continue
}
if strings.Compare(string(k[:headerPrefixLen]), string(config.HeaderPrefix)) == 0 {
key := k[headerPrefixLen:]
headers[string(key)] = v
}
delete(swamp, k)
}
httpConfig[string(checkHTTPHeadersAttr)] = headers
saveStringConfigToState(config.KeyFile, checkHTTPKeyFileAttr)
saveStringConfigToState(config.Method, checkHTTPMethodAttr)
saveStringConfigToState(config.Payload, checkHTTPPayloadAttr)
saveIntConfigToState(config.ReadLimit, checkHTTPReadLimitAttr)
saveStringConfigToState(config.URL, checkHTTPURLAttr)
saveStringConfigToState(config.HTTPVersion, checkHTTPVersionAttr)
whitelistedConfigKeys := map[config.Key]struct{}{
config.ReverseSecretKey: struct{}{},
config.SubmissionURL: struct{}{},
}
for k := range swamp {
if _, ok := whitelistedConfigKeys[k]; ok {
delete(c.Config, k)
}
if _, ok := whitelistedConfigKeys[k]; !ok {
return fmt.Errorf("PROVIDER BUG: API Config not empty: %#v", swamp)
}
}
if err := d.Set(checkHTTPAttr, schema.NewSet(hashCheckHTTP, []interface{}{httpConfig})); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkHTTPAttr), err)
}
return nil
}
// hashCheckHTTP creates a stable hash of the normalized values
func hashCheckHTTP(v interface{}) int {
m := v.(map[string]interface{})
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
writeInt := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok {
fmt.Fprintf(b, "%x", v.(int))
}
}
writeString := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok && v.(string) != "" {
fmt.Fprint(b, strings.TrimSpace(v.(string)))
}
}
// Order writes to the buffer using lexically sorted list for easy visual
// reconciliation with other lists.
writeString(checkHTTPAuthMethodAttr)
writeString(checkHTTPAuthPasswordAttr)
writeString(checkHTTPAuthUserAttr)
writeString(checkHTTPBodyRegexpAttr)
writeString(checkHTTPCAChainAttr)
writeString(checkHTTPCertFileAttr)
writeString(checkHTTPCiphersAttr)
writeString(checkHTTPCodeRegexpAttr)
writeString(checkHTTPExtractAttr)
if headersRaw, ok := m[string(checkHTTPHeadersAttr)]; ok {
headerMap := headersRaw.(map[string]interface{})
headers := make([]string, 0, len(headerMap))
for k := range headerMap {
headers = append(headers, k)
}
sort.Strings(headers)
for i := range headers {
fmt.Fprint(b, headers[i])
fmt.Fprint(b, headerMap[headers[i]].(string))
}
}
writeString(checkHTTPKeyFileAttr)
writeString(checkHTTPMethodAttr)
writeString(checkHTTPPayloadAttr)
writeInt(checkHTTPReadLimitAttr)
writeString(checkHTTPURLAttr)
writeString(checkHTTPVersionAttr)
s := b.String()
return hashcode.String(s)
}
func checkConfigToAPIHTTP(c *circonusCheck, l interfaceList) error {
c.Type = string(apiCheckTypeHTTP)
// Iterate over all `http` attributes, even though we have a max of 1 in the
// schema.
for _, mapRaw := range l {
httpConfig := newInterfaceMap(mapRaw)
if v, found := httpConfig[checkHTTPAuthMethodAttr]; found {
c.Config[config.AuthMethod] = v.(string)
}
if v, found := httpConfig[checkHTTPAuthPasswordAttr]; found {
c.Config[config.AuthPassword] = v.(string)
}
if v, found := httpConfig[checkHTTPAuthUserAttr]; found {
c.Config[config.AuthUser] = v.(string)
}
if v, found := httpConfig[checkHTTPBodyRegexpAttr]; found {
c.Config[config.Body] = v.(string)
}
if v, found := httpConfig[checkHTTPCAChainAttr]; found {
c.Config[config.CAChain] = v.(string)
}
if v, found := httpConfig[checkHTTPCertFileAttr]; found {
c.Config[config.CertFile] = v.(string)
}
if v, found := httpConfig[checkHTTPCiphersAttr]; found {
c.Config[config.Ciphers] = v.(string)
}
if v, found := httpConfig[checkHTTPCodeRegexpAttr]; found {
c.Config[config.Code] = v.(string)
}
if v, found := httpConfig[checkHTTPExtractAttr]; found {
c.Config[config.Extract] = v.(string)
}
if headers := httpConfig.CollectMap(checkHTTPHeadersAttr); headers != nil {
for k, v := range headers {
h := config.HeaderPrefix + config.Key(k)
c.Config[h] = v
}
}
if v, found := httpConfig[checkHTTPKeyFileAttr]; found {
c.Config[config.KeyFile] = v.(string)
}
if v, found := httpConfig[checkHTTPMethodAttr]; found {
c.Config[config.Method] = v.(string)
}
if v, found := httpConfig[checkHTTPPayloadAttr]; found {
c.Config[config.Payload] = v.(string)
}
if v, found := httpConfig[checkHTTPReadLimitAttr]; found {
c.Config[config.ReadLimit] = fmt.Sprintf("%d", v.(int))
}
if v, found := httpConfig[checkHTTPURLAttr]; found {
c.Config[config.URL] = v.(string)
u, _ := url.Parse(v.(string))
hostInfo := strings.SplitN(u.Host, ":", 2)
if len(c.Target) == 0 {
c.Target = hostInfo[0]
}
}
if v, found := httpConfig[checkHTTPVersionAttr]; found {
c.Config[config.HTTPVersion] = v.(string)
}
}
return nil
}

View File

@ -0,0 +1,184 @@
package circonus
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccCirconusCheckHTTP_basic(t *testing.T) {
checkName := fmt.Sprintf("Terraform test: noit's jezebel availability check - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckHTTPConfigFmt, checkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.jezebel", "active", "true"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "collector.2388330941.id", "/broker/1"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "http.#", "1"),
// resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.auth_method", ""),
// resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.auth_password", ""),
// resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.auth_user", ""),
// resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.body_regexp", ""),
// resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.ca_chain", ""),
// resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.certificate_file", ""),
// resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.ciphers", ""),
resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.code", `^200$`),
resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.extract", `HTTP/1.1 200 OK`),
// resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.key_file", ""),
// resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.payload", ""),
resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.headers.%", "1"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.headers.Host", "127.0.0.1"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.version", "1.1"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.method", "GET"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.read_limit", "1048576"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "http.4213422905.url", "http://127.0.0.1:8083/resmon"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.jezebel", "notes", "Check to make sure jezebel is working as expected"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.#", "4"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.42262635.active", "true"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.42262635.name", "code"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.42262635.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.42262635.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.42262635.tags.3219687752", "app:jezebel"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.42262635.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.42262635.tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.42262635.type", "text"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.1136493216.active", "true"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.1136493216.name", "duration"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.1136493216.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.1136493216.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.1136493216.tags.3219687752", "app:jezebel"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.1136493216.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.1136493216.tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.1136493216.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.1136493216.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.4246441943.active", "true"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.4246441943.name", "tt_connect"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.4246441943.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.4246441943.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.4246441943.tags.3219687752", "app:jezebel"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.4246441943.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.4246441943.tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.4246441943.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.4246441943.unit", "milliseconds"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.3695203246.active", "true"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.3695203246.name", "tt_firstbyte"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.3695203246.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.3695203246.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.3695203246.tags.3219687752", "app:jezebel"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.3695203246.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.3695203246.tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.3695203246.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "metric.3695203246.unit", "milliseconds"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "tags.3219687752", "app:jezebel"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "target", "127.0.0.1"),
resource.TestCheckResourceAttr("circonus_check.jezebel", "type", "http"),
),
},
},
})
}
const testAccCirconusCheckHTTPConfigFmt = `
variable "http_check_tags" {
type = "list"
default = [ "app:circonus", "app:jezebel", "lifecycle:unittests", "source:circonus" ]
}
resource "circonus_metric" "status_code" {
name = "code"
tags = [ "${var.http_check_tags}" ]
type = "text"
}
resource "circonus_metric" "request_duration" {
name = "duration"
tags = [ "${var.http_check_tags}" ]
type = "numeric"
unit = "seconds"
}
resource "circonus_metric" "request_ttconnect" {
name = "tt_connect"
tags = [ "${var.http_check_tags}" ]
type = "numeric"
unit = "milliseconds"
}
resource "circonus_metric" "request_ttfb" {
name = "tt_firstbyte"
tags = [ "${var.http_check_tags}" ]
type = "numeric"
unit = "milliseconds"
}
resource "circonus_check" "jezebel" {
active = true
name = "%s"
notes = "Check to make sure jezebel is working as expected"
period = "60s"
collector {
id = "/broker/1"
}
http {
code = "^200$"
extract = "HTTP/1.1 200 OK"
headers = {
Host = "127.0.0.1",
}
version = "1.1"
method = "GET"
read_limit = 1048576
url = "http://127.0.0.1:8083/resmon"
}
metric {
name = "${circonus_metric.status_code.name}"
tags = [ "${circonus_metric.status_code.tags}" ]
type = "${circonus_metric.status_code.type}"
}
metric {
name = "${circonus_metric.request_duration.name}"
tags = [ "${circonus_metric.request_duration.tags}" ]
type = "${circonus_metric.request_duration.type}"
unit = "${circonus_metric.request_duration.unit}"
}
metric {
name = "${circonus_metric.request_ttconnect.name}"
tags = [ "${circonus_metric.request_ttconnect.tags}" ]
type = "${circonus_metric.request_ttconnect.type}"
unit = "${circonus_metric.request_ttconnect.unit}"
}
metric {
name = "${circonus_metric.request_ttfb.name}"
tags = [ "${circonus_metric.request_ttfb.tags}" ]
type = "${circonus_metric.request_ttfb.type}"
unit = "${circonus_metric.request_ttfb.unit}"
}
tags = [ "${var.http_check_tags}" ]
}
`

View File

@ -0,0 +1,156 @@
package circonus
import (
"bytes"
"fmt"
"log"
"strings"
"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_check.httptrap.* resource attribute names
checkHTTPTrapAsyncMetricsAttr = "async_metrics"
checkHTTPTrapSecretAttr = "secret"
)
var checkHTTPTrapDescriptions = attrDescrs{
checkHTTPTrapAsyncMetricsAttr: "Specify whether httptrap metrics are logged immediately or held until the status message is emitted",
checkHTTPTrapSecretAttr: "",
}
var schemaCheckHTTPTrap = &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
MinItems: 1,
Set: hashCheckHTTPTrap,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkHTTPTrapDescriptions, map[schemaAttr]*schema.Schema{
checkHTTPTrapAsyncMetricsAttr: &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: defaultCheckHTTPTrapAsync,
},
checkHTTPTrapSecretAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ValidateFunc: validateRegexp(checkHTTPTrapSecretAttr, `^[a-zA-Z0-9_]+$`),
},
}),
},
}
// checkAPIToStateHTTPTrap reads the Config data out of circonusCheck.CheckBundle into
// the statefile.
func checkAPIToStateHTTPTrap(c *circonusCheck, d *schema.ResourceData) error {
httpTrapConfig := make(map[string]interface{}, len(c.Config))
// swamp is a sanity check: it must be empty by the time this method returns
swamp := make(map[config.Key]string, len(c.Config))
for k, v := range c.Config {
swamp[k] = v
}
saveBoolConfigToState := func(apiKey config.Key, attrName schemaAttr) {
if s, ok := c.Config[apiKey]; ok {
switch s {
case "true", "on":
httpTrapConfig[string(attrName)] = true
case "false", "off":
httpTrapConfig[string(attrName)] = false
default:
log.Printf("PROVIDER BUG: unsupported value %q returned in key %q", s, apiKey)
}
}
delete(swamp, apiKey)
}
saveStringConfigToState := func(apiKey config.Key, attrName schemaAttr) {
if s, ok := c.Config[apiKey]; ok {
httpTrapConfig[string(attrName)] = s
}
delete(swamp, apiKey)
}
saveBoolConfigToState(config.AsyncMetrics, checkHTTPTrapAsyncMetricsAttr)
saveStringConfigToState(config.Secret, checkHTTPTrapSecretAttr)
whitelistedConfigKeys := map[config.Key]struct{}{
config.ReverseSecretKey: struct{}{},
config.SubmissionURL: struct{}{},
}
for k := range swamp {
if _, ok := whitelistedConfigKeys[k]; ok {
delete(c.Config, k)
}
if _, ok := whitelistedConfigKeys[k]; !ok {
log.Printf("[ERROR]: PROVIDER BUG: API Config not empty: %#v", swamp)
}
}
if err := d.Set(checkHTTPTrapAttr, schema.NewSet(hashCheckHTTPTrap, []interface{}{httpTrapConfig})); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkHTTPTrapAttr), err)
}
return nil
}
// hashCheckHTTPTrap creates a stable hash of the normalized values
func hashCheckHTTPTrap(v interface{}) int {
m := v.(map[string]interface{})
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
writeBool := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok {
fmt.Fprintf(b, "%t", v.(bool))
}
}
writeString := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok && v.(string) != "" {
fmt.Fprint(b, strings.TrimSpace(v.(string)))
}
}
// Order writes to the buffer using lexically sorted list for easy visual
// reconciliation with other lists.
writeBool(checkHTTPTrapAsyncMetricsAttr)
writeString(checkHTTPTrapSecretAttr)
s := b.String()
return hashcode.String(s)
}
func checkConfigToAPIHTTPTrap(c *circonusCheck, l interfaceList) error {
c.Type = string(apiCheckTypeHTTPTrapAttr)
// Iterate over all `httptrap` attributes, even though we have a max of 1 in the
// schema.
for _, mapRaw := range l {
httpTrapConfig := newInterfaceMap(mapRaw)
if v, found := httpTrapConfig[checkHTTPTrapAsyncMetricsAttr]; found {
b := v.(bool)
if b {
c.Config[config.AsyncMetrics] = fmt.Sprintf("%t", b)
}
}
if v, found := httpTrapConfig[checkHTTPTrapSecretAttr]; found {
c.Config[config.Secret] = v.(string)
}
}
return nil
}

View File

@ -0,0 +1,120 @@
package circonus
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccCirconusCheckHTTPTrap_basic(t *testing.T) {
checkName := fmt.Sprintf("Terraform test: consul server httptrap check- %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckHTTPTrapConfigFmt, checkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.consul", "active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.consul", "collector.1893401625.id", "/broker/1286"),
resource.TestCheckResourceAttr("circonus_check.consul", "httptrap.#", "1"),
resource.TestCheckResourceAttr("circonus_check.consul", "httptrap.2067899660.async_metrics", "false"),
resource.TestCheckResourceAttr("circonus_check.consul", "httptrap.2067899660.secret", "12345"),
resource.TestCheckResourceAttr("circonus_check.consul", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.consul", "notes", "Check to receive consul server telemetry"),
resource.TestCheckResourceAttr("circonus_check.consul", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.#", "3"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2955331924.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2955331924.name", "consul`consul-server-10-151-2-8`consul`session_ttl`active"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2955331924.tags.#", "3"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2955331924.tags.3728194417", "app:consul"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2955331924.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2955331924.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2955331924.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2655764695.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2655764695.name", "consul`consul-server-10-151-2-8`runtime`alloc_bytes"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2655764695.tags.#", "3"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2655764695.tags.3728194417", "app:consul"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2655764695.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2655764695.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2655764695.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.2655764695.unit", "bytes"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.52765437.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.52765437.name", "consul`consul`http`GET`v1`kv`_"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.52765437.tags.#", "3"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.52765437.tags.3728194417", "app:consul"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.52765437.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.52765437.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.52765437.type", "histogram"),
resource.TestCheckResourceAttr("circonus_check.consul", "metric.52765437.unit", "nanoseconds"),
resource.TestCheckResourceAttr("circonus_check.consul", "tags.#", "3"),
resource.TestCheckResourceAttr("circonus_check.consul", "tags.3728194417", "app:consul"),
resource.TestCheckResourceAttr("circonus_check.consul", "tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.consul", "tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul", "target", "consul-server-10-151-2-8"),
resource.TestCheckResourceAttr("circonus_check.consul", "type", "httptrap"),
),
},
},
})
}
const testAccCirconusCheckHTTPTrapConfigFmt = `
variable "httptrap_check_tags" {
type = "list"
default = [ "app:consul", "lifecycle:unittests", "source:consul" ]
}
variable "consul_hostname" {
type = "string"
default = "consul-server-10-151-2-8"
}
resource "circonus_check" "consul" {
active = true
name = "%s"
notes = "Check to receive consul server telemetry"
period = "60s"
collector {
id = "/broker/1286"
}
httptrap {
async_metrics = "false"
secret = "12345"
}
metric {
name = "consul` + "`" + `${var.consul_hostname}` + "`" + `consul` + "`" + `session_ttl` + "`" + `active"
tags = [ "${var.httptrap_check_tags}" ]
type = "numeric"
}
metric {
name = "consul` + "`" + `${var.consul_hostname}` + "`" + `runtime` + "`" + `alloc_bytes"
tags = [ "${var.httptrap_check_tags}" ]
type = "numeric"
unit = "bytes"
}
metric {
name = "consul` + "`" + `consul` + "`" + `http` + "`" + `GET` + "`" + `v1` + "`" + `kv` + "`" + `_"
tags = [ "${var.httptrap_check_tags}" ]
type = "histogram"
unit = "nanoseconds"
}
tags = [ "${var.httptrap_check_tags}" ]
target = "${var.consul_hostname}"
}
`

View File

@ -0,0 +1,157 @@
package circonus
import (
"bytes"
"fmt"
"strconv"
"time"
"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_check.icmp_ping.* resource attribute names
checkICMPPingAvailabilityAttr = "availability"
checkICMPPingCountAttr = "count"
checkICMPPingIntervalAttr = "interval"
)
var checkICMPPingDescriptions = attrDescrs{
checkICMPPingAvailabilityAttr: `The percentage of ICMP available required for the check to be considered "good."`,
checkICMPPingCountAttr: "The number of ICMP requests to send during a single check.",
checkICMPPingIntervalAttr: "The number of milliseconds between ICMP requests.",
}
var schemaCheckICMPPing = &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
MinItems: 1,
Set: hashCheckICMPPing,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkICMPPingDescriptions, map[schemaAttr]*schema.Schema{
checkICMPPingAvailabilityAttr: &schema.Schema{
Type: schema.TypeFloat,
Optional: true,
Default: defaultCheckICMPPingAvailability,
ValidateFunc: validateFuncs(
validateFloatMin(checkICMPPingAvailabilityAttr, 0.0),
validateFloatMax(checkICMPPingAvailabilityAttr, 100.0),
),
},
checkICMPPingCountAttr: &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: defaultCheckICMPPingCount,
ValidateFunc: validateFuncs(
validateIntMin(checkICMPPingCountAttr, 0),
validateIntMax(checkICMPPingCountAttr, 20),
),
},
checkICMPPingIntervalAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCheckICMPPingInterval,
ValidateFunc: validateFuncs(
validateDurationMin(checkICMPPingIntervalAttr, "100µs"),
validateDurationMax(checkICMPPingIntervalAttr, "5m"),
),
},
}),
},
}
// checkAPIToStateICMPPing reads the Config data out of circonusCheck.CheckBundle
// into the statefile.
func checkAPIToStateICMPPing(c *circonusCheck, d *schema.ResourceData) error {
icmpPingConfig := make(map[string]interface{}, len(c.Config))
availNeeded, err := strconv.ParseFloat(c.Config[config.AvailNeeded], 64)
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to parse %s: {{err}}", config.AvailNeeded), err)
}
count, err := strconv.ParseInt(c.Config[config.Count], 10, 64)
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to parse %s: {{err}}", config.Count), err)
}
interval, err := time.ParseDuration(fmt.Sprintf("%sms", c.Config[config.Interval]))
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to parse %s: {{err}}", config.Interval), err)
}
icmpPingConfig[string(checkICMPPingAvailabilityAttr)] = availNeeded
icmpPingConfig[string(checkICMPPingCountAttr)] = int(count)
icmpPingConfig[string(checkICMPPingIntervalAttr)] = interval.String()
if err := d.Set(checkICMPPingAttr, schema.NewSet(hashCheckICMPPing, []interface{}{icmpPingConfig})); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkICMPPingAttr), err)
}
return nil
}
// hashCheckICMPPing creates a stable hash of the normalized values
func hashCheckICMPPing(v interface{}) int {
m := v.(map[string]interface{})
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
writeFloat64 := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok {
fmt.Fprintf(b, "%f", v.(float64))
}
}
writeInt := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok {
fmt.Fprintf(b, "%x", v.(int))
}
}
writeDuration := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok && v.(string) != "" {
d, _ := time.ParseDuration(v.(string))
fmt.Fprint(b, d.String())
}
}
// Order writes to the buffer using lexically sorted list for easy visual
// reconciliation with other lists.
writeFloat64(checkICMPPingAvailabilityAttr)
writeInt(checkICMPPingCountAttr)
writeDuration(checkICMPPingIntervalAttr)
s := b.String()
return hashcode.String(s)
}
func checkConfigToAPIICMPPing(c *circonusCheck, l interfaceList) error {
c.Type = string(apiCheckTypeICMPPing)
// Iterate over all `icmp_ping` attributes, even though we have a max of 1 in
// the schema.
for _, mapRaw := range l {
icmpPingConfig := newInterfaceMap(mapRaw)
if v, found := icmpPingConfig[checkICMPPingAvailabilityAttr]; found {
f := v.(float64)
c.Config[config.AvailNeeded] = fmt.Sprintf("%d", int(f))
}
if v, found := icmpPingConfig[checkICMPPingCountAttr]; found {
c.Config[config.Count] = fmt.Sprintf("%d", v.(int))
}
if v, found := icmpPingConfig[checkICMPPingIntervalAttr]; found {
d, _ := time.ParseDuration(v.(string))
c.Config[config.Interval] = fmt.Sprintf("%d", int64(d/time.Millisecond))
}
}
return nil
}

View File

@ -0,0 +1,137 @@
package circonus
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccCirconusCheckICMPPing_basic(t *testing.T) {
checkName := fmt.Sprintf("ICMP Ping check - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckICMPPingConfigFmt, checkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "active", "true"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "collector.2388330941.id", "/broker/1"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "icmp_ping.#", "1"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "icmp_ping.979664239.availability", "100"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "icmp_ping.979664239.count", "5"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "icmp_ping.979664239.interval", "500ms"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "period", "300s"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.#", "5"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.784357201.name", "available"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.784357201.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.784357201.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.784357201.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.784357201.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.784357201.unit", "%"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.3166992875.name", "average"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.3166992875.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.3166992875.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.3166992875.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.3166992875.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.3166992875.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.809361245.name", "count"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.809361245.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.809361245.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.809361245.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.809361245.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.809361245.unit", "packets"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.839816201.name", "maximum"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.839816201.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.839816201.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.839816201.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.839816201.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.839816201.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.1657693034.name", "minimum"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.1657693034.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.1657693034.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.1657693034.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.1657693034.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "metric.1657693034.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "target", "api.circonus.com"),
resource.TestCheckResourceAttr("circonus_check.loopback_latency", "type", "ping_icmp"),
),
},
},
})
}
const testAccCirconusCheckICMPPingConfigFmt = `
variable "test_tags" {
type = "list"
default = [ "author:terraform", "lifecycle:unittest" ]
}
resource "circonus_check" "loopback_latency" {
active = true
name = "%s"
period = "300s"
collector {
id = "/broker/1"
}
icmp_ping {
availability = "100.0"
count = 5
interval = "500ms"
}
metric {
name = "available"
tags = [ "${var.test_tags}" ]
type = "numeric"
unit = "%%"
}
metric {
name = "average"
tags = [ "${var.test_tags}" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "count"
tags = [ "${var.test_tags}" ]
type = "numeric"
unit = "packets"
}
metric {
name = "maximum"
tags = [ "${var.test_tags}" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "minimum"
tags = [ "${var.test_tags}" ]
type = "numeric"
unit = "seconds"
}
tags = [ "${var.test_tags}" ]
target = "api.circonus.com"
}
`

View File

@ -0,0 +1,366 @@
package circonus
import (
"bytes"
"fmt"
"log"
"net/url"
"sort"
"strconv"
"strings"
"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_check.json.* resource attribute names
checkJSONAuthMethodAttr = "auth_method"
checkJSONAuthPasswordAttr = "auth_password"
checkJSONAuthUserAttr = "auth_user"
checkJSONCAChainAttr = "ca_chain"
checkJSONCertFileAttr = "certificate_file"
checkJSONCiphersAttr = "ciphers"
checkJSONHeadersAttr = "headers"
checkJSONKeyFileAttr = "key_file"
checkJSONMethodAttr = "method"
checkJSONPayloadAttr = "payload"
checkJSONPortAttr = "port"
checkJSONReadLimitAttr = "read_limit"
checkJSONURLAttr = "url"
checkJSONVersionAttr = "version"
)
var checkJSONDescriptions = attrDescrs{
checkJSONAuthMethodAttr: "The HTTP Authentication method",
checkJSONAuthPasswordAttr: "The HTTP Authentication user password",
checkJSONAuthUserAttr: "The HTTP Authentication user name",
checkJSONCAChainAttr: "A path to a file containing all the certificate authorities that should be loaded to validate the remote certificate (for TLS checks)",
checkJSONCertFileAttr: "A path to a file containing the client certificate that will be presented to the remote server (for TLS-enabled checks)",
checkJSONCiphersAttr: "A list of ciphers to be used in the TLS protocol (for HTTPS checks)",
checkJSONHeadersAttr: "Map of HTTP Headers to send along with HTTP Requests",
checkJSONKeyFileAttr: "A path to a file containing key to be used in conjunction with the cilent certificate (for TLS checks)",
checkJSONMethodAttr: "The HTTP method to use",
checkJSONPayloadAttr: "The information transferred as the payload of an HTTP request",
checkJSONPortAttr: "Specifies the port on which the management interface can be reached",
checkJSONReadLimitAttr: "Sets an approximate limit on the data read (0 means no limit)",
checkJSONURLAttr: "The URL to use as the target of the check",
checkJSONVersionAttr: "Sets the HTTP version for the check to use",
}
var schemaCheckJSON = &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
MinItems: 1,
Set: checkJSONConfigChecksum,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkJSONDescriptions, map[schemaAttr]*schema.Schema{
checkJSONAuthMethodAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkJSONAuthMethodAttr, `^(?:Basic|Digest|Auto)$`),
},
checkJSONAuthPasswordAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ValidateFunc: validateRegexp(checkJSONAuthPasswordAttr, `^.*`),
},
checkJSONAuthUserAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkJSONAuthUserAttr, `[^:]+`),
},
checkJSONCAChainAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkJSONCAChainAttr, `.+`),
},
checkJSONCertFileAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkJSONCertFileAttr, `.+`),
},
checkJSONCiphersAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkJSONCiphersAttr, `.+`),
},
checkJSONHeadersAttr: &schema.Schema{
Type: schema.TypeMap,
Elem: schema.TypeString,
Optional: true,
ValidateFunc: validateHTTPHeaders,
},
checkJSONKeyFileAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkJSONKeyFileAttr, `.+`),
},
checkJSONMethodAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCheckJSONMethod,
ValidateFunc: validateRegexp(checkJSONMethodAttr, `\S+`),
},
checkJSONPayloadAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkJSONPayloadAttr, `\S+`),
},
checkJSONPortAttr: &schema.Schema{
Type: schema.TypeInt,
Default: defaultCheckJSONPort,
Optional: true,
ValidateFunc: validateFuncs(
validateIntMin(checkJSONPortAttr, 0),
validateIntMax(checkJSONPortAttr, 65535),
),
},
checkJSONReadLimitAttr: &schema.Schema{
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validateFuncs(
validateIntMin(checkJSONReadLimitAttr, 0),
),
},
checkJSONURLAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateFuncs(
validateHTTPURL(checkJSONURLAttr, urlIsAbs),
),
},
checkJSONVersionAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCheckJSONVersion,
ValidateFunc: validateStringIn(checkJSONVersionAttr, supportedHTTPVersions),
},
}),
},
}
// checkAPIToStateJSON reads the Config data out of circonusCheck.CheckBundle into
// the statefile.
func checkAPIToStateJSON(c *circonusCheck, d *schema.ResourceData) error {
jsonConfig := make(map[string]interface{}, len(c.Config))
// swamp is a sanity check: it must be empty by the time this method returns
swamp := make(map[config.Key]string, len(c.Config))
for k, s := range c.Config {
swamp[k] = s
}
saveStringConfigToState := func(apiKey config.Key, attrName schemaAttr) {
if s, ok := c.Config[apiKey]; ok && s != "" {
jsonConfig[string(attrName)] = s
}
delete(swamp, apiKey)
}
saveIntConfigToState := func(apiKey config.Key, attrName schemaAttr) {
if s, ok := c.Config[apiKey]; ok && s != "0" {
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
log.Printf("[ERROR]: Unable to convert %s to an integer: %v", apiKey, err)
return
}
jsonConfig[string(attrName)] = int(i)
}
delete(swamp, apiKey)
}
saveStringConfigToState(config.AuthMethod, checkJSONAuthMethodAttr)
saveStringConfigToState(config.AuthPassword, checkJSONAuthPasswordAttr)
saveStringConfigToState(config.AuthUser, checkJSONAuthUserAttr)
saveStringConfigToState(config.CAChain, checkJSONCAChainAttr)
saveStringConfigToState(config.CertFile, checkJSONCertFileAttr)
saveStringConfigToState(config.Ciphers, checkJSONCiphersAttr)
headers := make(map[string]interface{}, len(c.Config))
headerPrefixLen := len(config.HeaderPrefix)
for k, v := range c.Config {
if len(k) <= headerPrefixLen {
continue
}
if strings.Compare(string(k[:headerPrefixLen]), string(config.HeaderPrefix)) == 0 {
key := k[headerPrefixLen:]
headers[string(key)] = v
}
delete(swamp, k)
}
jsonConfig[string(checkJSONHeadersAttr)] = headers
saveStringConfigToState(config.KeyFile, checkJSONKeyFileAttr)
saveStringConfigToState(config.Method, checkJSONMethodAttr)
saveStringConfigToState(config.Payload, checkJSONPayloadAttr)
saveIntConfigToState(config.Port, checkJSONPortAttr)
saveIntConfigToState(config.ReadLimit, checkJSONReadLimitAttr)
saveStringConfigToState(config.URL, checkJSONURLAttr)
saveStringConfigToState(config.HTTPVersion, checkJSONVersionAttr)
whitelistedConfigKeys := map[config.Key]struct{}{
config.ReverseSecretKey: struct{}{},
config.SubmissionURL: struct{}{},
}
for k := range swamp {
if _, ok := whitelistedConfigKeys[k]; ok {
delete(c.Config, k)
}
if _, ok := whitelistedConfigKeys[k]; !ok {
return fmt.Errorf("PROVIDER BUG: API Config not empty: %#v", swamp)
}
}
if err := d.Set(checkJSONAttr, schema.NewSet(checkJSONConfigChecksum, []interface{}{jsonConfig})); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkJSONAttr), err)
}
return nil
}
// checkJSONConfigChecksum creates a stable hash of the normalized values found
// in a user's Terraform config.
func checkJSONConfigChecksum(v interface{}) int {
m := v.(map[string]interface{})
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
writeInt := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok && v.(int) != 0 {
fmt.Fprintf(b, "%x", v.(int))
}
}
writeString := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok && v.(string) != "" {
fmt.Fprint(b, strings.TrimSpace(v.(string)))
}
}
// Order writes to the buffer using lexically sorted list for easy visual
// reconciliation with other lists.
writeString(checkJSONAuthMethodAttr)
writeString(checkJSONAuthPasswordAttr)
writeString(checkJSONAuthUserAttr)
writeString(checkJSONCAChainAttr)
writeString(checkJSONCertFileAttr)
writeString(checkJSONCiphersAttr)
if headersRaw, ok := m[string(checkJSONHeadersAttr)]; ok {
headerMap := headersRaw.(map[string]interface{})
headers := make([]string, 0, len(headerMap))
for k := range headerMap {
headers = append(headers, k)
}
sort.Strings(headers)
for i := range headers {
fmt.Fprint(b, headers[i])
fmt.Fprint(b, headerMap[headers[i]].(string))
}
}
writeString(checkJSONKeyFileAttr)
writeString(checkJSONMethodAttr)
writeString(checkJSONPayloadAttr)
writeInt(checkJSONPortAttr)
writeInt(checkJSONReadLimitAttr)
writeString(checkJSONURLAttr)
writeString(checkJSONVersionAttr)
s := b.String()
return hashcode.String(s)
}
func checkConfigToAPIJSON(c *circonusCheck, l interfaceList) error {
c.Type = string(apiCheckTypeJSON)
// Iterate over all `json` attributes, even though we have a max of 1 in the
// schema.
for _, mapRaw := range l {
jsonConfig := newInterfaceMap(mapRaw)
if v, found := jsonConfig[checkJSONAuthMethodAttr]; found {
c.Config[config.AuthMethod] = v.(string)
}
if v, found := jsonConfig[checkJSONAuthPasswordAttr]; found {
c.Config[config.AuthPassword] = v.(string)
}
if v, found := jsonConfig[checkJSONAuthUserAttr]; found {
c.Config[config.AuthUser] = v.(string)
}
if v, found := jsonConfig[checkJSONCAChainAttr]; found {
c.Config[config.CAChain] = v.(string)
}
if v, found := jsonConfig[checkJSONCertFileAttr]; found {
c.Config[config.CertFile] = v.(string)
}
if v, found := jsonConfig[checkJSONCiphersAttr]; found {
c.Config[config.Ciphers] = v.(string)
}
if headers := jsonConfig.CollectMap(checkJSONHeadersAttr); headers != nil {
for k, v := range headers {
h := config.HeaderPrefix + config.Key(k)
c.Config[h] = v
}
}
if v, found := jsonConfig[checkJSONKeyFileAttr]; found {
c.Config[config.KeyFile] = v.(string)
}
if v, found := jsonConfig[checkJSONMethodAttr]; found {
c.Config[config.Method] = v.(string)
}
if v, found := jsonConfig[checkJSONPayloadAttr]; found {
c.Config[config.Payload] = v.(string)
}
if v, found := jsonConfig[checkJSONPortAttr]; found {
i := v.(int)
if i != 0 {
c.Config[config.Port] = fmt.Sprintf("%d", i)
}
}
if v, found := jsonConfig[checkJSONReadLimitAttr]; found {
i := v.(int)
if i != 0 {
c.Config[config.ReadLimit] = fmt.Sprintf("%d", i)
}
}
if v, found := jsonConfig[checkJSONURLAttr]; found {
c.Config[config.URL] = v.(string)
u, _ := url.Parse(v.(string))
hostInfo := strings.SplitN(u.Host, ":", 2)
if len(c.Target) == 0 {
c.Target = hostInfo[0]
}
}
if v, found := jsonConfig[checkJSONVersionAttr]; found {
c.Config[config.HTTPVersion] = v.(string)
}
}
return nil
}

View File

@ -0,0 +1,230 @@
package circonus
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccCirconusCheckJSON_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: testAccCirconusCheckJSONConfig1,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.usage", "active", "true"),
resource.TestCheckResourceAttr("circonus_check.usage", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.usage", "collector.2388330941.id", "/broker/1"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.#", "1"),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.auth_method", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.auth_password", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.auth_user", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.ca_chain", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.certificate_file", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.ciphers", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.key_file", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.payload", ""),
resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.headers.%", "3"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.headers.Accept", "application/json"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.headers.X-Circonus-App-Name", "TerraformCheck"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.headers.X-Circonus-Auth-Token", "<env 'CIRCONUS_API_TOKEN'>"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.version", "1.0"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.method", "GET"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.port", "443"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.read_limit", "1048576"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.2626248092.url", "https://api.circonus.com/account/current"),
resource.TestCheckResourceAttr("circonus_check.usage", "name", "Terraform test: api.circonus.com metric usage check"),
resource.TestCheckResourceAttr("circonus_check.usage", "notes", ""),
resource.TestCheckResourceAttr("circonus_check.usage", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.#", "2"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.active", "true"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.name", "_usage`0`_limit"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.tags.#", "1"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.unit", "qty"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.active", "true"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.name", "_usage`0`_used"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.tags.#", "1"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.unit", "qty"),
resource.TestCheckResourceAttr("circonus_check.usage", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.usage", "tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_check.usage", "tags.3839162439", "source:unittest"),
resource.TestCheckResourceAttr("circonus_check.usage", "target", "api.circonus.com"),
resource.TestCheckResourceAttr("circonus_check.usage", "type", "json"),
),
},
{
Config: testAccCirconusCheckJSONConfig2,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.usage", "active", "true"),
resource.TestCheckResourceAttr("circonus_check.usage", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.usage", "collector.2388330941.id", "/broker/1"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.#", "1"),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.auth_method", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.auth_password", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.auth_user", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.ca_chain", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.certificate_file", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.ciphers", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.key_file", ""),
// resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.payload", ""),
resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.headers.%", "3"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.headers.Accept", "application/json"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.headers.X-Circonus-App-Name", "TerraformCheck"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.headers.X-Circonus-Auth-Token", "<env 'CIRCONUS_API_TOKEN'>"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.version", "1.1"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.method", "GET"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.port", "443"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.read_limit", "1048576"),
resource.TestCheckResourceAttr("circonus_check.usage", "json.3951979786.url", "https://api.circonus.com/account/current"),
resource.TestCheckResourceAttr("circonus_check.usage", "name", "Terraform test: api.circonus.com metric usage check"),
resource.TestCheckResourceAttr("circonus_check.usage", "notes", "notes!"),
resource.TestCheckResourceAttr("circonus_check.usage", "period", "300s"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.#", "2"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.active", "true"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.name", "_usage`0`_limit"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.tags.#", "1"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.1992097900.unit", "qty"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.active", "true"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.name", "_usage`0`_used"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.tags.#", "1"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.usage", "metric.3280673139.unit", "qty"),
resource.TestCheckResourceAttr("circonus_check.usage", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.usage", "tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_check.usage", "tags.3839162439", "source:unittest"),
resource.TestCheckResourceAttr("circonus_check.usage", "target", "api.circonus.com"),
resource.TestCheckResourceAttr("circonus_check.usage", "type", "json"),
),
},
},
})
}
const testAccCirconusCheckJSONConfig1 = `
variable "usage_default_unit" {
default = "qty"
}
resource "circonus_metric" "limit" {
name = "_usage` + "`0`" + `_limit"
tags = [ "source:circonus" ]
type = "numeric"
unit = "${var.usage_default_unit}"
}
resource "circonus_metric" "used" {
name = "_usage` + "`0`" + `_used"
tags = [ "source:circonus" ]
type = "numeric"
unit = "${var.usage_default_unit}"
}
resource "circonus_check" "usage" {
active = true
name = "Terraform test: api.circonus.com metric usage check"
period = "60s"
collector {
id = "/broker/1"
}
json {
url = "https://api.circonus.com/account/current"
headers = {
Accept = "application/json",
X-Circonus-App-Name = "TerraformCheck",
X-Circonus-Auth-Token = "<env 'CIRCONUS_API_TOKEN'>",
}
version = "1.0"
method = "GET"
port = 443
read_limit = 1048576
}
metric {
name = "${circonus_metric.used.name}"
tags = [ "${circonus_metric.used.tags}" ]
type = "${circonus_metric.used.type}"
unit = "${coalesce(circonus_metric.used.unit, var.usage_default_unit)}"
}
metric {
name = "${circonus_metric.limit.name}"
tags = [ "${circonus_metric.limit.tags}" ]
type = "${circonus_metric.limit.type}"
unit = "${coalesce(circonus_metric.limit.unit, var.usage_default_unit)}"
}
tags = [ "source:circonus", "source:unittest" ]
}
`
const testAccCirconusCheckJSONConfig2 = `
variable "usage_default_unit" {
default = "qty"
}
resource "circonus_metric" "limit" {
name = "_usage` + "`0`" + `_limit"
tags = [ "source:circonus" ]
type = "numeric"
unit = "${var.usage_default_unit}"
}
resource "circonus_metric" "used" {
name = "_usage` + "`0`" + `_used"
tags = [ "source:circonus" ]
type = "numeric"
unit = "${var.usage_default_unit}"
}
resource "circonus_check" "usage" {
active = true
name = "Terraform test: api.circonus.com metric usage check"
notes = "notes!"
period = "300s"
collector {
id = "/broker/1"
}
json {
url = "https://api.circonus.com/account/current"
headers = {
Accept = "application/json",
X-Circonus-App-Name = "TerraformCheck",
X-Circonus-Auth-Token = "<env 'CIRCONUS_API_TOKEN'>",
}
version = "1.1"
method = "GET"
port = 443
read_limit = 1048576
}
metric {
name = "${circonus_metric.used.name}"
tags = [ "${circonus_metric.used.tags}" ]
type = "${circonus_metric.used.type}"
unit = "${coalesce(circonus_metric.used.unit, var.usage_default_unit)}"
}
metric {
name = "${circonus_metric.limit.name}"
tags = [ "${circonus_metric.limit.tags}" ]
type = "${circonus_metric.limit.type}"
unit = "${coalesce(circonus_metric.limit.unit, var.usage_default_unit)}"
}
tags = [ "source:circonus", "source:unittest" ]
}
`

View File

@ -0,0 +1,102 @@
package circonus
import (
"bytes"
"fmt"
"strings"
"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_check.mysql.* resource attribute names
checkMySQLDSNAttr = "dsn"
checkMySQLQueryAttr = "query"
)
var checkMySQLDescriptions = attrDescrs{
checkMySQLDSNAttr: "The connect DSN for the MySQL instance",
checkMySQLQueryAttr: "The SQL to use as the query",
}
var schemaCheckMySQL = &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
MinItems: 1,
Set: hashCheckMySQL,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkMySQLDescriptions, map[schemaAttr]*schema.Schema{
checkMySQLDSNAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRegexp(checkMySQLDSNAttr, `^.+$`),
},
checkMySQLQueryAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
StateFunc: func(v interface{}) string { return strings.TrimSpace(v.(string)) },
ValidateFunc: validateRegexp(checkMySQLQueryAttr, `.+`),
},
}),
},
}
// checkAPIToStateMySQL reads the Config data out of circonusCheck.CheckBundle into the
// statefile.
func checkAPIToStateMySQL(c *circonusCheck, d *schema.ResourceData) error {
MySQLConfig := make(map[string]interface{}, len(c.Config))
MySQLConfig[string(checkMySQLDSNAttr)] = c.Config[config.DSN]
MySQLConfig[string(checkMySQLQueryAttr)] = c.Config[config.SQL]
if err := d.Set(checkMySQLAttr, schema.NewSet(hashCheckMySQL, []interface{}{MySQLConfig})); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkMySQLAttr), err)
}
return nil
}
// hashCheckMySQL creates a stable hash of the normalized values
func hashCheckMySQL(v interface{}) int {
m := v.(map[string]interface{})
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
writeString := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok && v.(string) != "" {
fmt.Fprint(b, strings.TrimSpace(v.(string)))
}
}
// Order writes to the buffer using lexically sorted list for easy visual
// reconciliation with other lists.
writeString(checkMySQLDSNAttr)
writeString(checkMySQLQueryAttr)
s := b.String()
return hashcode.String(s)
}
func checkConfigToAPIMySQL(c *circonusCheck, l interfaceList) error {
c.Type = string(apiCheckTypeMySQL)
// Iterate over all `postgres` attributes, even though we have a max of 1 in
// the schema.
for _, mapRaw := range l {
mysqlConfig := newInterfaceMap(mapRaw)
if v, found := mysqlConfig[checkMySQLDSNAttr]; found {
c.Config[config.DSN] = v.(string)
}
if v, found := mysqlConfig[checkMySQLQueryAttr]; found {
c.Config[config.SQL] = v.(string)
}
}
return nil
}

View File

@ -0,0 +1,80 @@
package circonus
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccCirconusCheckMySQL_basic(t *testing.T) {
checkName := fmt.Sprintf("MySQL binlog total - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckMySQLConfigFmt, checkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.table_ops", "active", "true"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "collector.2388330941.id", "/broker/1"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "mysql.#", "1"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "mysql.3110376931.dsn", "user=mysql host=mydb1.example.org port=3306 password=12345 sslmode=require"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "mysql.3110376931.query", `select 'binlog', total from (select variable_value as total from information_schema.global_status where variable_name='BINLOG_CACHE_USE') total`),
resource.TestCheckResourceAttr("circonus_check.table_ops", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.table_ops", "period", "300s"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.#", "1"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.885029470.name", "binlog`total"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.885029470.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.885029470.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.885029470.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.885029470.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "target", "mydb.example.org"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "type", "mysql"),
),
},
},
})
}
const testAccCirconusCheckMySQLConfigFmt = `
variable "test_tags" {
type = "list"
default = [ "author:terraform", "lifecycle:unittest" ]
}
resource "circonus_check" "table_ops" {
active = true
name = "%s"
period = "300s"
collector {
id = "/broker/1"
}
mysql {
dsn = "user=mysql host=mydb1.example.org port=3306 password=12345 sslmode=require"
query = <<EOF
select 'binlog', total from (select variable_value as total from information_schema.global_status where variable_name='BINLOG_CACHE_USE') total
EOF
}
metric {
name = "binlog` + "`" + `total"
tags = [ "${var.test_tags}" ]
type = "numeric"
}
tags = [ "${var.test_tags}" ]
target = "mydb.example.org"
}
`

View File

@ -0,0 +1,166 @@
package circonus
import (
"bytes"
"fmt"
"strings"
"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_check.postgresql.* resource attribute names
checkPostgreSQLDSNAttr = "dsn"
// checkPostgreSQLHostAttr = "host"
// checkPostgreSQLNameAttr = "name"
// checkPostgreSQLPasswordAttr = "password"
// checkPostgreSQLPortAttr = "port"
checkPostgreSQLQueryAttr = "query"
// checkPostgreSQLSSLModeAttr = "sslmode"
// checkPostgreSQLUserAttr = "user"
)
var checkPostgreSQLDescriptions = attrDescrs{
checkPostgreSQLDSNAttr: "The connect DSN for the PostgreSQL instance",
// checkPostgreSQLHostAttr: "The Hostname to connect to",
// checkPostgreSQLNameAttr: "The database name to connect to",
// checkPostgreSQLPasswordAttr: "The password to use",
// checkPostgreSQLPortAttr: "The TCP port number to use to connect on",
checkPostgreSQLQueryAttr: "The SQL to use as the query",
// checkPostgreSQLSSLModeAttr: "The SSL Mode to connect as",
// checkPostgreSQLUserAttr: "The username to connect as",
}
var schemaCheckPostgreSQL = &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
MinItems: 1,
Set: hashCheckPostgreSQL,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkPostgreSQLDescriptions, map[schemaAttr]*schema.Schema{
checkPostgreSQLDSNAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRegexp(checkPostgreSQLDSNAttr, `^.+$`),
},
// TODO(sean@): Parse out the DSN into individual PostgreSQL connect
// options.
//
// checkPostgreSQLHostAttr: &schema.Schema{
// Type: schema.TypeString,
// Optional: true,
// Default: "/tmp",
// ValidateFunc: validateRegexp(checkPostgreSQLHostAttr, `^(/.+|[\S]+)$`),
// },
// checkPostgreSQLNameAttr: &schema.Schema{
// Type: schema.TypeString,
// Required: true,
// ValidateFunc: validateRegexp(checkPostgreSQLNameAttr, `^[\S]+$`),
// },
// checkPostgreSQLPasswordAttr: &schema.Schema{
// Type: schema.TypeString,
// Optional: true,
// Sensitive: true,
// },
// checkPostgreSQLPortAttr: &schema.Schema{
// Type: schema.TypeInt,
// Optional: true,
// Default: 5432,
// ValidateFunc: validateFuncs(
// validateIntMin(checkPostgreSQLPortAttr, 1),
// validateIntMax(checkPostgreSQLPortAttr, 65535),
// ),
// },
checkPostgreSQLQueryAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
StateFunc: suppressWhitespace,
ValidateFunc: validateRegexp(checkPostgreSQLQueryAttr, `.+`),
},
// checkPostgreSQLSSLModeAttr: &schema.Schema{
// Type: schema.TypeString,
// Optional: true,
// Default: "require",
// ValidateFunc: validateRegexp(checkPostgreSQLSSLModeAttr, `^(disable|require|verify-ca|verify-full)$`),
// },
// checkPostgreSQLUserAttr: &schema.Schema{
// Type: schema.TypeString,
// Required: true,
// ValidateFunc: validateRegexp(checkPostgreSQLUserAttr, `.+`),
// },
}),
},
}
// checkAPIToStatePostgreSQL reads the Config data out of circonusCheck.CheckBundle into the
// statefile.
func checkAPIToStatePostgreSQL(c *circonusCheck, d *schema.ResourceData) error {
postgresqlConfig := make(map[string]interface{}, len(c.Config))
// TODO(sean@): Parse out the DSN into individual PostgreSQL connect options
postgresqlConfig[string(checkPostgreSQLDSNAttr)] = c.Config[config.DSN]
postgresqlConfig[string(checkPostgreSQLQueryAttr)] = c.Config[config.SQL]
if err := d.Set(checkPostgreSQLAttr, schema.NewSet(hashCheckPostgreSQL, []interface{}{postgresqlConfig})); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkPostgreSQLAttr), err)
}
return nil
}
// hashCheckPostgreSQL creates a stable hash of the normalized values
func hashCheckPostgreSQL(v interface{}) int {
m := v.(map[string]interface{})
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
// writeInt := func(attrName schemaAttr) {
// if v, ok := m[string(attrName)]; ok {
// fmt.Fprintf(b, "%x", v.(int))
// }
// }
writeString := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok && v.(string) != "" {
fmt.Fprint(b, strings.TrimSpace(v.(string)))
}
}
// Order writes to the buffer using lexically sorted list for easy visual
// reconciliation with other lists.
writeString(checkPostgreSQLDSNAttr)
// writeString(checkPostgreSQLHostAttr)
// writeString(checkPostgreSQLNameAttr)
// writeString(checkPostgreSQLPasswordAttr)
// writeInt(checkPostgreSQLPortAttr)
// writeString(checkPostgreSQLSSLModeAttr)
writeString(checkPostgreSQLQueryAttr)
// writeString(checkPostgreSQLUserAttr)
s := b.String()
return hashcode.String(s)
}
func checkConfigToAPIPostgreSQL(c *circonusCheck, l interfaceList) error {
c.Type = string(apiCheckTypePostgreSQL)
// Iterate over all `postgres` attributes, even though we have a max of 1 in
// the schema.
for _, mapRaw := range l {
postgresConfig := newInterfaceMap(mapRaw)
if v, found := postgresConfig[checkPostgreSQLDSNAttr]; found {
c.Config[config.DSN] = v.(string)
}
if v, found := postgresConfig[checkPostgreSQLQueryAttr]; found {
c.Config[config.SQL] = v.(string)
}
}
return nil
}

View File

@ -0,0 +1,151 @@
package circonus
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccCirconusCheckPostgreSQL_basic(t *testing.T) {
checkName := fmt.Sprintf("PostgreSQL ops per table check - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckPostgreSQLConfigFmt, checkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.table_ops", "active", "true"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "collector.2388330941.id", "/broker/1"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "postgresql.#", "1"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "postgresql.1831600166.dsn", "user=postgres host=pg1.example.org port=5432 password=12345 sslmode=require"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "postgresql.1831600166.query", `SELECT 'tables', sum(n_tup_ins) as inserts, sum(n_tup_upd) as updates, sum(n_tup_del) as deletes, sum(idx_scan) as index_scans, sum(seq_scan) as seq_scans, sum(idx_tup_fetch) as index_tup_fetch, sum(seq_tup_read) as seq_tup_read from pg_stat_all_tables`),
resource.TestCheckResourceAttr("circonus_check.table_ops", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.table_ops", "period", "300s"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.#", "7"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.997634628.name", "tables`inserts"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.997634628.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.997634628.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.997634628.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.997634628.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.565883273.name", "tables`updates"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.565883273.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.565883273.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.565883273.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.565883273.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3965415003.name", "tables`deletes"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3965415003.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3965415003.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3965415003.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3965415003.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3868690100.name", "tables`index_scans"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3868690100.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3868690100.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3868690100.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3868690100.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.2772400178.name", "tables`seq_scans"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.2772400178.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.2772400178.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.2772400178.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.2772400178.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3278042831.name", "tables`index_tup_fetch"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3278042831.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3278042831.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3278042831.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.3278042831.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.2685537214.name", "tables`seq_tup_read"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.2685537214.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.2685537214.tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.2685537214.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "metric.2685537214.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "target", "pgdb.example.org"),
resource.TestCheckResourceAttr("circonus_check.table_ops", "type", "postgres"),
),
},
},
})
}
const testAccCirconusCheckPostgreSQLConfigFmt = `
variable "test_tags" {
type = "list"
default = [ "author:terraform", "lifecycle:unittest" ]
}
resource "circonus_check" "table_ops" {
active = true
name = "%s"
period = "300s"
collector {
id = "/broker/1"
}
postgresql {
dsn = "user=postgres host=pg1.example.org port=5432 password=12345 sslmode=require"
query = <<EOF
SELECT 'tables', sum(n_tup_ins) as inserts, sum(n_tup_upd) as updates, sum(n_tup_del) as deletes, sum(idx_scan) as index_scans, sum(seq_scan) as seq_scans, sum(idx_tup_fetch) as index_tup_fetch, sum(seq_tup_read) as seq_tup_read from pg_stat_all_tables
EOF
}
metric {
name = "tables` + "`" + `inserts"
tags = [ "${var.test_tags}" ]
type = "numeric"
}
metric {
name = "tables` + "`" + `updates"
tags = [ "${var.test_tags}" ]
type = "numeric"
}
metric {
name = "tables` + "`" + `deletes"
tags = [ "${var.test_tags}" ]
type = "numeric"
}
metric {
name = "tables` + "`" + `index_scans"
tags = [ "${var.test_tags}" ]
type = "numeric"
}
metric {
name = "tables` + "`" + `seq_scans"
tags = [ "${var.test_tags}" ]
type = "numeric"
}
metric {
name = "tables` + "`" + `index_tup_fetch"
tags = [ "${var.test_tags}" ]
type = "numeric"
}
metric {
name = "tables` + "`" + `seq_tup_read"
tags = [ "${var.test_tags}" ]
type = "numeric"
}
tags = [ "${var.test_tags}" ]
target = "pgdb.example.org"
}
`

View File

@ -0,0 +1,254 @@
package circonus
import (
"bytes"
"fmt"
"log"
"strconv"
"strings"
"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_check.tcp.* resource attribute names
checkTCPBannerRegexpAttr = "banner_regexp"
checkTCPCAChainAttr = "ca_chain"
checkTCPCertFileAttr = "certificate_file"
checkTCPCiphersAttr = "ciphers"
checkTCPHostAttr = "host"
checkTCPKeyFileAttr = "key_file"
checkTCPPortAttr = "port"
checkTCPTLSAttr = "tls"
)
var checkTCPDescriptions = attrDescrs{
checkTCPBannerRegexpAttr: `This regular expression is matched against the response banner. If a match is not found, the check will be marked as bad.`,
checkTCPCAChainAttr: "A path to a file containing all the certificate authorities that should be loaded to validate the remote certificate (for TLS checks).",
checkTCPCertFileAttr: "A path to a file containing the client certificate that will be presented to the remote server (for TLS checks).",
checkTCPCiphersAttr: "A list of ciphers to be used when establishing a TLS connection",
checkTCPHostAttr: "Specifies the host name or IP address to connect to for this TCP check",
checkTCPKeyFileAttr: "A path to a file containing key to be used in conjunction with the cilent certificate (for TLS checks)",
checkTCPPortAttr: "Specifies the port on which the management interface can be reached.",
checkTCPTLSAttr: "Upgrade TCP connection to use TLS.",
}
var schemaCheckTCP = &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MaxItems: 1,
MinItems: 1,
Set: hashCheckTCP,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkTCPDescriptions, map[schemaAttr]*schema.Schema{
checkTCPBannerRegexpAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkTCPBannerRegexpAttr, `.+`),
},
checkTCPCAChainAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkTCPCAChainAttr, `.+`),
},
checkTCPCertFileAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkTCPCertFileAttr, `.+`),
},
checkTCPCiphersAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkTCPCiphersAttr, `.+`),
},
checkTCPHostAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRegexp(checkTCPHostAttr, `.+`),
},
checkTCPKeyFileAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkTCPKeyFileAttr, `.+`),
},
checkTCPPortAttr: &schema.Schema{
Type: schema.TypeInt,
Required: true,
ValidateFunc: validateFuncs(
validateIntMin(checkTCPPortAttr, 0),
validateIntMax(checkTCPPortAttr, 65535),
),
},
checkTCPTLSAttr: &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
}),
},
}
// checkAPIToStateTCP reads the Config data out of circonusCheck.CheckBundle into the
// statefile.
func checkAPIToStateTCP(c *circonusCheck, d *schema.ResourceData) error {
tcpConfig := make(map[string]interface{}, len(c.Config))
// swamp is a sanity check: it must be empty by the time this method returns
swamp := make(map[config.Key]string, len(c.Config))
for k, v := range c.Config {
swamp[k] = v
}
saveBoolConfigToState := func(apiKey config.Key, attrName schemaAttr) {
if s, ok := c.Config[apiKey]; ok {
switch strings.ToLower(s) {
case "1", "true", "t", "yes", "y":
tcpConfig[string(attrName)] = true
case "0", "false", "f", "no", "n":
tcpConfig[string(attrName)] = false
default:
log.Printf("PROVIDER BUG: unsupported boolean: %q for API Config Key %q", s, string(apiKey))
return
}
}
delete(swamp, apiKey)
}
saveIntConfigToState := func(apiKey config.Key, attrName schemaAttr) {
if v, ok := c.Config[apiKey]; ok {
i, err := strconv.ParseInt(v, 10, 64)
if err != nil {
log.Printf("[ERROR]: Unable to convert %s to an integer: %v", apiKey, err)
return
}
tcpConfig[string(attrName)] = int(i)
}
delete(swamp, apiKey)
}
saveStringConfigToState := func(apiKey config.Key, attrName schemaAttr) {
if v, ok := c.Config[apiKey]; ok {
tcpConfig[string(attrName)] = v
}
delete(swamp, apiKey)
}
saveStringConfigToState(config.BannerMatch, checkTCPBannerRegexpAttr)
saveStringConfigToState(config.CAChain, checkTCPCAChainAttr)
saveStringConfigToState(config.CertFile, checkTCPCertFileAttr)
saveStringConfigToState(config.Ciphers, checkTCPCiphersAttr)
tcpConfig[string(checkTCPHostAttr)] = c.Target
saveStringConfigToState(config.KeyFile, checkTCPKeyFileAttr)
saveIntConfigToState(config.Port, checkTCPPortAttr)
saveBoolConfigToState(config.UseSSL, checkTCPTLSAttr)
whitelistedConfigKeys := map[config.Key]struct{}{
config.ReverseSecretKey: struct{}{},
config.SubmissionURL: struct{}{},
}
for k := range swamp {
if _, ok := whitelistedConfigKeys[k]; ok {
delete(c.Config, k)
}
if _, ok := whitelistedConfigKeys[k]; !ok {
log.Printf("[ERROR]: PROVIDER BUG: API Config not empty: %#v", swamp)
}
}
if err := d.Set(checkTCPAttr, schema.NewSet(hashCheckTCP, []interface{}{tcpConfig})); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkTCPAttr), err)
}
return nil
}
// hashCheckTCP creates a stable hash of the normalized values
func hashCheckTCP(v interface{}) int {
m := v.(map[string]interface{})
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
writeBool := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok {
fmt.Fprintf(b, "%t", v.(bool))
}
}
writeInt := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok {
fmt.Fprintf(b, "%x", v.(int))
}
}
writeString := func(attrName schemaAttr) {
if v, ok := m[string(attrName)]; ok && v.(string) != "" {
fmt.Fprint(b, strings.TrimSpace(v.(string)))
}
}
// Order writes to the buffer using lexically sorted list for easy visual
// reconciliation with other lists.
writeString(checkTCPBannerRegexpAttr)
writeString(checkTCPCAChainAttr)
writeString(checkTCPCertFileAttr)
writeString(checkTCPCiphersAttr)
writeString(checkTCPHostAttr)
writeString(checkTCPKeyFileAttr)
writeInt(checkTCPPortAttr)
writeBool(checkTCPTLSAttr)
s := b.String()
return hashcode.String(s)
}
func checkConfigToAPITCP(c *circonusCheck, l interfaceList) error {
c.Type = string(apiCheckTypeTCP)
// Iterate over all `tcp` attributes, even though we have a max of 1 in the
// schema.
for _, mapRaw := range l {
tcpConfig := newInterfaceMap(mapRaw)
if v, found := tcpConfig[checkTCPBannerRegexpAttr]; found {
c.Config[config.BannerMatch] = v.(string)
}
if v, found := tcpConfig[checkTCPCAChainAttr]; found {
c.Config[config.CAChain] = v.(string)
}
if v, found := tcpConfig[checkTCPCertFileAttr]; found {
c.Config[config.CertFile] = v.(string)
}
if v, found := tcpConfig[checkTCPCiphersAttr]; found {
c.Config[config.Ciphers] = v.(string)
}
if v, found := tcpConfig[checkTCPHostAttr]; found {
c.Target = v.(string)
}
if v, found := tcpConfig[checkTCPKeyFileAttr]; found {
c.Config[config.KeyFile] = v.(string)
}
if v, found := tcpConfig[checkTCPPortAttr]; found {
c.Config[config.Port] = fmt.Sprintf("%d", v.(int))
}
if v, found := tcpConfig[checkTCPTLSAttr]; found {
c.Config[config.UseSSL] = fmt.Sprintf("%t", v.(bool))
}
}
return nil
}

View File

@ -0,0 +1,224 @@
package circonus
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccCirconusCheckTCP_basic(t *testing.T) {
checkName := fmt.Sprintf("Terraform test: TCP+TLS check - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckTCPConfigFmt, checkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.tls_cert", "active", "true"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "collector.1893401625.id", "/broker/1286"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "tcp.#", "1"),
// resource.TestCheckResourceAttr("circonus_check.tls_cert", "tcp.453641246.banner_regexp", ""),
// resource.TestCheckResourceAttr("circonus_check.tls_cert", "tcp.453641246.ca_chain", ""),
// resource.TestCheckResourceAttr("circonus_check.tls_cert", "tcp.453641246.certificate_file", ""),
// resource.TestCheckResourceAttr("circonus_check.tls_cert", "tcp.453641246.ciphers", ""),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "tcp.453641246.host", "127.0.0.1"),
// resource.TestCheckResourceAttr("circonus_check.tls_cert", "tcp.453641246.key_file", ""),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "tcp.453641246.port", "443"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "notes", "Check to harvest cert expiration information"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.#", "9"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.2951598908.active", "true"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.2951598908.name", "cert_end"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.2951598908.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.2951598908.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.2951598908.tags.213659730", "app:tls_cert"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.2951598908.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.2951598908.tags.862116066", "source:fastly"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.2951598908.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.2951598908.unit", "epoch"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.4072382121.active", "true"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.4072382121.name", "cert_end_in"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.4072382121.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.4072382121.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.4072382121.tags.213659730", "app:tls_cert"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.4072382121.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.4072382121.tags.862116066", "source:fastly"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.4072382121.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.4072382121.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3384170740.active", "true"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3384170740.name", "cert_error"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3384170740.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3384170740.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3384170740.tags.213659730", "app:tls_cert"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3384170740.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3384170740.tags.862116066", "source:fastly"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3384170740.type", "text"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3384170740.unit", ""),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.979255163.active", "true"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.979255163.name", "cert_issuer"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.979255163.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.979255163.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.979255163.tags.213659730", "app:tls_cert"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.979255163.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.979255163.tags.862116066", "source:fastly"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.979255163.type", "text"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.979255163.unit", ""),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1378403576.active", "true"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1378403576.name", "cert_start"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1378403576.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1378403576.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1378403576.tags.213659730", "app:tls_cert"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1378403576.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1378403576.tags.862116066", "source:fastly"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1378403576.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1378403576.unit", "epoch"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1662016973.active", "true"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1662016973.name", "cert_subject"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1662016973.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1662016973.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1662016973.tags.213659730", "app:tls_cert"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1662016973.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1662016973.tags.862116066", "source:fastly"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1662016973.type", "text"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.1662016973.unit", ""),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.872453198.active", "true"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.872453198.name", "duration"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.872453198.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.872453198.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.872453198.tags.213659730", "app:tls_cert"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.872453198.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.872453198.tags.862116066", "source:fastly"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.872453198.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.872453198.unit", "milliseconds"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.719003215.active", "true"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.719003215.name", "tt_connect"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.719003215.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.719003215.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.719003215.tags.213659730", "app:tls_cert"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.719003215.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.719003215.tags.862116066", "source:fastly"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.719003215.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.719003215.unit", "milliseconds"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3321090683.active", "true"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3321090683.name", "tt_firstbyte"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3321090683.tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3321090683.tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3321090683.tags.213659730", "app:tls_cert"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3321090683.tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3321090683.tags.862116066", "source:fastly"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3321090683.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "metric.3321090683.unit", "milliseconds"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "tags.#", "4"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "tags.30226350", "app:circonus"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "tags.213659730", "app:tls_cert"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "tags.1543130091", "lifecycle:unittests"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "tags.862116066", "source:fastly"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "target", "127.0.0.1"),
resource.TestCheckResourceAttr("circonus_check.tls_cert", "type", "tcp"),
),
},
},
})
}
const testAccCirconusCheckTCPConfigFmt = `
variable "tcp_check_tags" {
type = "list"
default = [ "app:circonus", "app:tls_cert", "lifecycle:unittests", "source:fastly" ]
}
resource "circonus_check" "tls_cert" {
active = true
name = "%s"
notes = "Check to harvest cert expiration information"
period = "60s"
collector {
id = "/broker/1286"
}
tcp {
host = "127.0.0.1"
port = 443
}
metric {
name = "cert_end"
tags = [ "${var.tcp_check_tags}" ]
type = "numeric"
unit = "epoch"
}
metric {
name = "cert_end_in"
tags = [ "${var.tcp_check_tags}" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "cert_error"
tags = [ "${var.tcp_check_tags}" ]
type = "text"
}
metric {
name = "cert_issuer"
tags = [ "${var.tcp_check_tags}" ]
type = "text"
}
metric {
name = "cert_start"
tags = [ "${var.tcp_check_tags}" ]
type = "numeric"
unit = "epoch"
}
metric {
name = "cert_subject"
tags = [ "${var.tcp_check_tags}" ]
type = "text"
}
metric {
name = "duration"
tags = [ "${var.tcp_check_tags}" ]
type = "numeric"
unit = "milliseconds"
}
metric {
name = "tt_connect"
tags = [ "${var.tcp_check_tags}" ]
type = "numeric"
unit = "milliseconds"
}
metric {
name = "tt_firstbyte"
tags = [ "${var.tcp_check_tags}" ]
type = "numeric"
unit = "milliseconds"
}
tags = [ "${var.tcp_check_tags}" ]
}
`

View File

@ -0,0 +1,43 @@
package circonus
import (
"fmt"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/hashicorp/terraform/terraform"
)
func testAccCheckDestroyCirconusCheckBundle(s *terraform.State) error {
c := testAccProvider.Meta().(*providerContext)
for _, rs := range s.RootModule().Resources {
if rs.Type != "circonus_check" {
continue
}
cid := rs.Primary.ID
exists, err := checkCheckBundleExists(c, api.CIDType(&cid))
if err != nil {
return fmt.Errorf("Error checking check bundle %s", err)
}
if exists {
return fmt.Errorf("check bundle still exists after destroy")
}
}
return nil
}
func checkCheckBundleExists(c *providerContext, checkBundleID api.CIDType) (bool, error) {
cb, err := c.client.FetchCheckBundle(checkBundleID)
if err != nil {
return false, err
}
if api.CIDType(&cb.CID) == checkBundleID {
return true, nil
}
return false, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,241 @@
package circonus
import (
"fmt"
"strings"
"testing"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccCirconusContactGroup_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusContactGroup,
Steps: []resource.TestStep{
{
Config: testAccCirconusContactGroupConfig,
Check: resource.ComposeTestCheckFunc(
// testAccContactGroupExists("circonus_contact_group.staging-sev3", "foo"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "name", "ops-staging-sev3"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "email.#", "3"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "email.1119127802.address", ""),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "email.1119127802.user", "/user/5469"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "email.1456570992.address", ""),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "email.1456570992.user", "/user/6331"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "email.343263208.address", "user@example.com"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "email.343263208.user", ""),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "http.#", "1"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "http.1287846151.address", "https://www.example.org/post/endpoint"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "http.1287846151.format", "json"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "http.1287846151.method", "POST"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "irc.#", "0"),
// resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "irc.918937268.user", "/user/6331"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "slack.#", "1"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "slack.274933206.channel", "#ops-staging"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "slack.274933206.team", "T123UT98F"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "slack.274933206.username", "Circonus"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "slack.274933206.buttons", "true"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "sms.#", "1"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "sms.1119127802.user", "/user/5469"),
// xmpp.# will be 0 for user faux user accounts that don't have an
// XMPP address setup.
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "xmpp.#", "0"),
// resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "xmpp.1119127802.user", "/user/5469"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "victorops.#", "1"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "victorops.2029434450.api_key", "123"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "victorops.2029434450.critical", "2"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "victorops.2029434450.info", "5"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "victorops.2029434450.team", "bender"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "victorops.2029434450.warning", "3"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "aggregation_window", "60s"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.#", "5"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.689365425.severity", "1"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.689365425.reminder", "60s"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.689365425.escalate_after", "3600s"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.689365425.escalate_to", "/contact_group/2913"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.551050940.severity", "2"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.551050940.reminder", "120s"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.551050940.escalate_after", "7200s"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.551050940.escalate_to", "/contact_group/2913"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.1292974544.severity", "3"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.1292974544.reminder", "180s"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.1292974544.escalate_after", "10800s"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.1292974544.escalate_to", "/contact_group/2913"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.1183354841.severity", "4"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.1183354841.reminder", "240s"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.1183354841.escalate_after", "14400s"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.1183354841.escalate_to", "/contact_group/2913"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.2942620849.severity", "5"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.2942620849.reminder", "300s"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.2942620849.escalate_after", "18000s"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "alert_option.2942620849.escalate_to", "/contact_group/2913"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "long_message", "a long message"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "long_subject", "long subject"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "long_summary", "long summary"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "short_message", "short message"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "short_summary", "short summary"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_contact_group.staging-sev3", "tags.393923453", "other:foo"),
),
},
},
})
}
func testAccCheckDestroyCirconusContactGroup(s *terraform.State) error {
c := testAccProvider.Meta().(*providerContext)
for _, rs := range s.RootModule().Resources {
if rs.Type != "circonus_contact_group" {
continue
}
cid := rs.Primary.ID
exists, err := checkContactGroupExists(c, api.CIDType(&cid))
switch {
case !exists:
// noop
case exists:
return fmt.Errorf("contact group still exists after destroy")
case err != nil:
return fmt.Errorf("Error checking contact group %s", err)
}
}
return nil
}
func checkContactGroupExists(c *providerContext, contactGroupCID api.CIDType) (bool, error) {
cb, err := c.client.FetchContactGroup(contactGroupCID)
if err != nil {
if strings.Contains(err.Error(), defaultCirconus404ErrorString) {
return false, nil
}
return false, err
}
if api.CIDType(&cb.CID) == contactGroupCID {
return true, nil
}
return false, nil
}
const testAccCirconusContactGroupConfig = `
resource "circonus_contact_group" "staging-sev3" {
name = "ops-staging-sev3"
email {
user = "/user/5469"
}
email {
address = "user@example.com"
}
email {
user = "/user/6331"
}
http {
address = "https://www.example.org/post/endpoint"
format = "json"
method = "POST"
}
/*
// Account needs to be setup with IRC before this can work.
irc {
user = "/user/6331"
}
*/
/*
pagerduty {
// NOTE(sean@): needs to be filled in
}
*/
slack {
channel = "#ops-staging"
team = "T123UT98F"
username = "Circonus"
buttons = true
}
sms {
user = "/user/5469"
}
victorops {
api_key = "123"
critical = 2
info = 5
team = "bender"
warning = 3
}
// Faux user accounts that don't have an XMPP address setup will not return a
// valid response in the future.
//
// xmpp {
// user = "/user/5469"
// }
aggregation_window = "1m"
alert_option {
severity = 1
reminder = "60s"
escalate_after = "3600s"
escalate_to = "/contact_group/2913"
}
alert_option {
severity = 2
reminder = "2m"
escalate_after = "2h"
escalate_to = "/contact_group/2913"
}
alert_option {
severity = 3
reminder = "3m"
escalate_after = "3h"
escalate_to = "/contact_group/2913"
}
alert_option {
severity = 4
reminder = "4m"
escalate_after = "4h"
escalate_to = "/contact_group/2913"
}
alert_option {
severity = 5
reminder = "5m"
escalate_after = "5h"
escalate_to = "/contact_group/2913"
}
// alert_formats: omit to use defaults
long_message = "a long message"
long_subject = "long subject"
long_summary = "long summary"
short_message = "short message"
short_summary = "short summary"
tags = [
"author:terraform",
"other:foo",
]
}
`

View File

@ -0,0 +1,930 @@
package circonus
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/circonus-labs/circonus-gometrics/api/config"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)
const (
// circonus_graph.* resource attribute names
graphDescriptionAttr = "description"
graphLeftAttr = "left"
graphLineStyleAttr = "line_style"
graphMetricClusterAttr = "metric_cluster"
graphNameAttr = "name"
graphNotesAttr = "notes"
graphRightAttr = "right"
graphMetricAttr = "metric"
graphStyleAttr = "graph_style"
graphTagsAttr = "tags"
// circonus_graph.metric.* resource attribute names
graphMetricActiveAttr = "active"
graphMetricAlphaAttr = "alpha"
graphMetricAxisAttr = "axis"
graphMetricCAQLAttr = "caql"
graphMetricCheckAttr = "check"
graphMetricColorAttr = "color"
graphMetricFormulaAttr = "formula"
graphMetricFormulaLegendAttr = "legend_formula"
graphMetricFunctionAttr = "function"
graphMetricHumanNameAttr = "name"
graphMetricMetricTypeAttr = "metric_type"
graphMetricNameAttr = "metric_name"
graphMetricStackAttr = "stack"
// circonus_graph.metric_cluster.* resource attribute names
graphMetricClusterActiveAttr = "active"
graphMetricClusterAggregateAttr = "aggregate"
graphMetricClusterAxisAttr = "axis"
graphMetricClusterColorAttr = "color"
graphMetricClusterQueryAttr = "query"
graphMetricClusterHumanNameAttr = "name"
// circonus_graph.{left,right}.* resource attribute names
graphAxisLogarithmicAttr = "logarithmic"
graphAxisMaxAttr = "max"
graphAxisMinAttr = "min"
)
const (
apiGraphStyleLine = "line"
)
var graphDescriptions = attrDescrs{
// circonus_graph.* resource attribute names
graphDescriptionAttr: "",
graphLeftAttr: "",
graphLineStyleAttr: "How the line should change between point. A string containing either 'stepped', 'interpolated' or null.",
graphNameAttr: "",
graphNotesAttr: "",
graphRightAttr: "",
graphMetricAttr: "",
graphMetricClusterAttr: "",
graphStyleAttr: "",
graphTagsAttr: "",
}
var graphMetricDescriptions = attrDescrs{
// circonus_graph.metric.* resource attribute names
graphMetricActiveAttr: "",
graphMetricAlphaAttr: "",
graphMetricAxisAttr: "",
graphMetricCAQLAttr: "",
graphMetricCheckAttr: "",
graphMetricColorAttr: "",
graphMetricFormulaAttr: "",
graphMetricFormulaLegendAttr: "",
graphMetricFunctionAttr: "",
graphMetricMetricTypeAttr: "",
graphMetricHumanNameAttr: "",
graphMetricNameAttr: "",
graphMetricStackAttr: "",
}
var graphMetricClusterDescriptions = attrDescrs{
// circonus_graph.metric_cluster.* resource attribute names
graphMetricClusterActiveAttr: "",
graphMetricClusterAggregateAttr: "",
graphMetricClusterAxisAttr: "",
graphMetricClusterColorAttr: "",
graphMetricClusterQueryAttr: "",
graphMetricClusterHumanNameAttr: "",
}
// NOTE(sean@): There is no way to set a description on map inputs, but if that
// does happen:
//
// var graphMetricAxisOptionDescriptions = attrDescrs{
// // circonus_graph.if.value.over.* resource attribute names
// graphAxisLogarithmicAttr: "",
// graphAxisMaxAttr: "",
// graphAxisMinAttr: "",
// }
func resourceGraph() *schema.Resource {
makeConflictsWith := func(in ...schemaAttr) []string {
out := make([]string, 0, len(in))
for _, attr := range in {
out = append(out, string(graphMetricAttr)+"."+string(attr))
}
return out
}
return &schema.Resource{
Create: graphCreate,
Read: graphRead,
Update: graphUpdate,
Delete: graphDelete,
Exists: graphExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: convertToHelperSchema(graphDescriptions, map[schemaAttr]*schema.Schema{
graphDescriptionAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
StateFunc: suppressWhitespace,
},
graphLeftAttr: &schema.Schema{
Type: schema.TypeMap,
Elem: schema.TypeString,
Optional: true,
ValidateFunc: validateGraphAxisOptions,
},
graphLineStyleAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultGraphLineStyle,
ValidateFunc: validateStringIn(graphLineStyleAttr, validGraphLineStyles),
},
graphNameAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRegexp(graphNameAttr, `.+`),
},
graphNotesAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
graphRightAttr: &schema.Schema{
Type: schema.TypeMap,
Elem: schema.TypeString,
Optional: true,
ValidateFunc: validateGraphAxisOptions,
},
graphMetricAttr: &schema.Schema{
Type: schema.TypeList,
Optional: true,
MinItems: 1,
Elem: &schema.Resource{
Schema: convertToHelperSchema(graphMetricDescriptions, map[schemaAttr]*schema.Schema{
graphMetricActiveAttr: &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
graphMetricAlphaAttr: &schema.Schema{
Type: schema.TypeFloat,
Optional: true,
ValidateFunc: validateFuncs(
validateFloatMin(graphMetricAlphaAttr, 0.0),
validateFloatMax(graphMetricAlphaAttr, 1.0),
),
},
graphMetricAxisAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "left",
ValidateFunc: validateStringIn(graphMetricAxisAttr, validAxisAttrs),
},
graphMetricCAQLAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(graphMetricCAQLAttr, `.+`),
ConflictsWith: makeConflictsWith(graphMetricCheckAttr, graphMetricNameAttr),
},
graphMetricCheckAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(graphMetricCheckAttr, config.CheckCIDRegex),
ConflictsWith: makeConflictsWith(graphMetricCAQLAttr),
},
graphMetricColorAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(graphMetricColorAttr, `^#[0-9a-fA-F]{6}$`),
},
graphMetricFormulaAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(graphMetricFormulaAttr, `^.+$`),
},
graphMetricFormulaLegendAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(graphMetricFormulaLegendAttr, `^.+$`),
},
graphMetricFunctionAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultGraphFunction,
ValidateFunc: validateStringIn(graphMetricFunctionAttr, validGraphFunctionValues),
},
graphMetricMetricTypeAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateStringIn(graphMetricMetricTypeAttr, validMetricTypes),
},
graphMetricHumanNameAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(graphMetricHumanNameAttr, `.+`),
},
graphMetricNameAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(graphMetricNameAttr, `^[\S]+$`),
},
graphMetricStackAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(graphMetricStackAttr, `^[\d]*$`),
},
}),
},
},
graphMetricClusterAttr: &schema.Schema{
Type: schema.TypeList,
Optional: true,
MinItems: 1,
Elem: &schema.Resource{
Schema: convertToHelperSchema(graphMetricClusterDescriptions, map[schemaAttr]*schema.Schema{
graphMetricClusterActiveAttr: &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
graphMetricClusterAggregateAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "none",
ValidateFunc: validateStringIn(graphMetricClusterAggregateAttr, validAggregateFuncs),
},
graphMetricClusterAxisAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "left",
ValidateFunc: validateStringIn(graphMetricClusterAttr, validAxisAttrs),
},
graphMetricClusterColorAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(graphMetricClusterColorAttr, `^#[0-9a-fA-F]{6}$`),
},
graphMetricClusterQueryAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(graphMetricClusterQueryAttr, config.MetricClusterCIDRegex),
},
graphMetricClusterHumanNameAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRegexp(graphMetricHumanNameAttr, `.+`),
},
}),
},
},
graphStyleAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultGraphStyle,
ValidateFunc: validateStringIn(graphStyleAttr, validGraphStyles),
},
graphTagsAttr: tagMakeConfigSchema(graphTagsAttr),
}),
}
}
func graphCreate(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
g := newGraph()
if err := g.ParseConfig(d); err != nil {
return errwrap.Wrapf("error parsing graph schema during create: {{err}}", err)
}
if err := g.Create(ctxt); err != nil {
return errwrap.Wrapf("error creating graph: {{err}}", err)
}
d.SetId(g.CID)
return graphRead(d, meta)
}
func graphExists(d *schema.ResourceData, meta interface{}) (bool, error) {
ctxt := meta.(*providerContext)
cid := d.Id()
g, err := ctxt.client.FetchGraph(api.CIDType(&cid))
if err != nil {
if strings.Contains(err.Error(), defaultCirconus404ErrorString) {
return false, nil
}
return false, err
}
if g.CID == "" {
return false, nil
}
return true, nil
}
// graphRead pulls data out of the Graph object and stores it into the
// appropriate place in the statefile.
func graphRead(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
cid := d.Id()
g, err := loadGraph(ctxt, api.CIDType(&cid))
if err != nil {
return err
}
d.SetId(g.CID)
metrics := make([]interface{}, 0, len(g.Datapoints))
for _, datapoint := range g.Datapoints {
dataPointAttrs := make(map[string]interface{}, 13) // 13 == len(members in api.GraphDatapoint)
dataPointAttrs[string(graphMetricActiveAttr)] = !datapoint.Hidden
if datapoint.Alpha != nil && *datapoint.Alpha != 0 {
dataPointAttrs[string(graphMetricAlphaAttr)] = *datapoint.Alpha
}
switch datapoint.Axis {
case "l", "":
dataPointAttrs[string(graphMetricAxisAttr)] = "left"
case "r":
dataPointAttrs[string(graphMetricAxisAttr)] = "right"
default:
return fmt.Errorf("PROVIDER BUG: Unsupported axis type %q", datapoint.Axis)
}
if datapoint.CAQL != nil {
dataPointAttrs[string(graphMetricCAQLAttr)] = *datapoint.CAQL
}
if datapoint.CheckID != 0 {
dataPointAttrs[string(graphMetricCheckAttr)] = fmt.Sprintf("%s/%d", config.CheckPrefix, datapoint.CheckID)
}
if datapoint.Color != nil {
dataPointAttrs[string(graphMetricColorAttr)] = *datapoint.Color
}
if datapoint.DataFormula != nil {
dataPointAttrs[string(graphMetricFormulaAttr)] = *datapoint.DataFormula
}
switch datapoint.Derive.(type) {
case bool:
case string:
dataPointAttrs[string(graphMetricFunctionAttr)] = datapoint.Derive.(string)
default:
return fmt.Errorf("PROVIDER BUG: Unsupported type for derive: %T", datapoint.Derive)
}
if datapoint.LegendFormula != nil {
dataPointAttrs[string(graphMetricFormulaLegendAttr)] = *datapoint.LegendFormula
}
if datapoint.MetricName != "" {
dataPointAttrs[string(graphMetricNameAttr)] = datapoint.MetricName
}
if datapoint.MetricType != "" {
dataPointAttrs[string(graphMetricMetricTypeAttr)] = datapoint.MetricType
}
if datapoint.Name != "" {
dataPointAttrs[string(graphMetricHumanNameAttr)] = datapoint.Name
}
if datapoint.Stack != nil {
dataPointAttrs[string(graphMetricStackAttr)] = fmt.Sprintf("%d", *datapoint.Stack)
}
metrics = append(metrics, dataPointAttrs)
}
metricClusters := make([]interface{}, 0, len(g.MetricClusters))
for _, metricCluster := range g.MetricClusters {
metricClusterAttrs := make(map[string]interface{}, 8) // 8 == len(num struct attrs in api.GraphMetricCluster)
metricClusterAttrs[string(graphMetricClusterActiveAttr)] = !metricCluster.Hidden
if metricCluster.AggregateFunc != "" {
metricClusterAttrs[string(graphMetricClusterAggregateAttr)] = metricCluster.AggregateFunc
}
switch metricCluster.Axis {
case "l", "":
metricClusterAttrs[string(graphMetricClusterAxisAttr)] = "left"
case "r":
metricClusterAttrs[string(graphMetricClusterAxisAttr)] = "right"
default:
return fmt.Errorf("PROVIDER BUG: Unsupported axis type %q", metricCluster.Axis)
}
if metricCluster.Color != nil {
metricClusterAttrs[string(graphMetricClusterColorAttr)] = *metricCluster.Color
}
if metricCluster.DataFormula != nil {
metricClusterAttrs[string(graphMetricFormulaAttr)] = *metricCluster.DataFormula
}
if metricCluster.LegendFormula != nil {
metricClusterAttrs[string(graphMetricFormulaLegendAttr)] = *metricCluster.LegendFormula
}
if metricCluster.MetricCluster != "" {
metricClusterAttrs[string(graphMetricClusterQueryAttr)] = metricCluster.MetricCluster
}
if metricCluster.Name != "" {
metricClusterAttrs[string(graphMetricHumanNameAttr)] = metricCluster.Name
}
if metricCluster.Stack != nil {
metricClusterAttrs[string(graphMetricStackAttr)] = fmt.Sprintf("%d", *metricCluster.Stack)
}
metricClusters = append(metricClusters, metricClusterAttrs)
}
leftAxisMap := make(map[string]interface{}, 3)
if g.LogLeftY != nil {
leftAxisMap[string(graphAxisLogarithmicAttr)] = fmt.Sprintf("%d", *g.LogLeftY)
}
if g.MaxLeftY != nil {
leftAxisMap[string(graphAxisMaxAttr)] = strconv.FormatFloat(*g.MaxLeftY, 'f', -1, 64)
}
if g.MinLeftY != nil {
leftAxisMap[string(graphAxisMinAttr)] = strconv.FormatFloat(*g.MinLeftY, 'f', -1, 64)
}
rightAxisMap := make(map[string]interface{}, 3)
if g.LogRightY != nil {
rightAxisMap[string(graphAxisLogarithmicAttr)] = fmt.Sprintf("%d", *g.LogRightY)
}
if g.MaxRightY != nil {
rightAxisMap[string(graphAxisMaxAttr)] = strconv.FormatFloat(*g.MaxRightY, 'f', -1, 64)
}
if g.MinRightY != nil {
rightAxisMap[string(graphAxisMinAttr)] = strconv.FormatFloat(*g.MinRightY, 'f', -1, 64)
}
d.Set(graphDescriptionAttr, g.Description)
if err := d.Set(graphLeftAttr, leftAxisMap); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store graph %q attribute: {{err}}", graphLeftAttr), err)
}
d.Set(graphLineStyleAttr, g.LineStyle)
d.Set(graphNameAttr, g.Title)
d.Set(graphNotesAttr, indirect(g.Notes))
if err := d.Set(graphRightAttr, rightAxisMap); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store graph %q attribute: {{err}}", graphRightAttr), err)
}
if err := d.Set(graphMetricAttr, metrics); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store graph %q attribute: {{err}}", graphMetricAttr), err)
}
if err := d.Set(graphMetricClusterAttr, metricClusters); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store graph %q attribute: {{err}}", graphMetricClusterAttr), err)
}
d.Set(graphStyleAttr, g.Style)
if err := d.Set(graphTagsAttr, tagsToState(apiToTags(g.Tags))); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store graph %q attribute: {{err}}", graphTagsAttr), err)
}
return nil
}
func graphUpdate(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
g := newGraph()
if err := g.ParseConfig(d); err != nil {
return err
}
g.CID = d.Id()
if err := g.Update(ctxt); err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to update graph %q: {{err}}", d.Id()), err)
}
return graphRead(d, meta)
}
func graphDelete(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
cid := d.Id()
if _, err := ctxt.client.DeleteGraphByCID(api.CIDType(&cid)); err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to delete graph %q: {{err}}", d.Id()), err)
}
d.SetId("")
return nil
}
type circonusGraph struct {
api.Graph
}
func newGraph() circonusGraph {
g := circonusGraph{
Graph: *api.NewGraph(),
}
return g
}
func loadGraph(ctxt *providerContext, cid api.CIDType) (circonusGraph, error) {
var g circonusGraph
ng, err := ctxt.client.FetchGraph(cid)
if err != nil {
return circonusGraph{}, err
}
g.Graph = *ng
return g, nil
}
// ParseConfig reads Terraform config data and stores the information into a
// Circonus Graph object. ParseConfig and graphRead() must be kept in sync.
func (g *circonusGraph) ParseConfig(d *schema.ResourceData) error {
g.Datapoints = make([]api.GraphDatapoint, 0, defaultGraphDatapoints)
if v, found := d.GetOk(graphLeftAttr); found {
listRaw := v.(map[string]interface{})
leftAxisMap := make(map[string]interface{}, len(listRaw))
for k, v := range listRaw {
leftAxisMap[k] = v
}
if v, ok := leftAxisMap[string(graphAxisLogarithmicAttr)]; ok {
i64, _ := strconv.ParseInt(v.(string), 10, 64)
i := int(i64)
g.LogLeftY = &i
}
if v, ok := leftAxisMap[string(graphAxisMaxAttr)]; ok && v.(string) != "" {
f, _ := strconv.ParseFloat(v.(string), 64)
g.MaxLeftY = &f
}
if v, ok := leftAxisMap[string(graphAxisMinAttr)]; ok && v.(string) != "" {
f, _ := strconv.ParseFloat(v.(string), 64)
g.MinLeftY = &f
}
}
if v, found := d.GetOk(graphRightAttr); found {
listRaw := v.(map[string]interface{})
rightAxisMap := make(map[string]interface{}, len(listRaw))
for k, v := range listRaw {
rightAxisMap[k] = v
}
if v, ok := rightAxisMap[string(graphAxisLogarithmicAttr)]; ok {
i64, _ := strconv.ParseInt(v.(string), 10, 64)
i := int(i64)
g.LogRightY = &i
}
if v, ok := rightAxisMap[string(graphAxisMaxAttr)]; ok && v.(string) != "" {
f, _ := strconv.ParseFloat(v.(string), 64)
g.MaxRightY = &f
}
if v, ok := rightAxisMap[string(graphAxisMinAttr)]; ok && v.(string) != "" {
f, _ := strconv.ParseFloat(v.(string), 64)
g.MinRightY = &f
}
}
if v, found := d.GetOk(graphDescriptionAttr); found {
g.Description = v.(string)
}
if v, found := d.GetOk(graphLineStyleAttr); found {
switch v.(type) {
case string:
s := v.(string)
g.LineStyle = &s
case *string:
g.LineStyle = v.(*string)
default:
return fmt.Errorf("PROVIDER BUG: unsupported type for %q: %T", graphLineStyleAttr, v)
}
}
if v, found := d.GetOk(graphNameAttr); found {
g.Title = v.(string)
}
if v, found := d.GetOk(graphNotesAttr); found {
s := v.(string)
g.Notes = &s
}
if listRaw, found := d.GetOk(graphMetricAttr); found {
metricList := listRaw.([]interface{})
for _, metricListElem := range metricList {
metricAttrs := newInterfaceMap(metricListElem.(map[string]interface{}))
datapoint := api.GraphDatapoint{}
if v, found := metricAttrs[graphMetricActiveAttr]; found {
datapoint.Hidden = !(v.(bool))
}
if v, found := metricAttrs[graphMetricAlphaAttr]; found {
f := v.(float64)
if f != 0 {
datapoint.Alpha = &f
}
}
if v, found := metricAttrs[graphMetricAxisAttr]; found {
switch v.(string) {
case "left", "":
datapoint.Axis = "l"
case "right":
datapoint.Axis = "r"
default:
return fmt.Errorf("PROVIDER BUG: Unsupported axis attribute %q: %q", graphMetricAxisAttr, v.(string))
}
}
if v, found := metricAttrs[graphMetricCheckAttr]; found {
re := regexp.MustCompile(config.CheckCIDRegex)
matches := re.FindStringSubmatch(v.(string))
if len(matches) == 3 {
checkID, _ := strconv.ParseUint(matches[2], 10, 64)
datapoint.CheckID = uint(checkID)
}
}
if v, found := metricAttrs[graphMetricColorAttr]; found {
s := v.(string)
datapoint.Color = &s
}
if v, found := metricAttrs[graphMetricFormulaAttr]; found {
switch v.(type) {
case string:
s := v.(string)
datapoint.DataFormula = &s
case *string:
datapoint.DataFormula = v.(*string)
default:
return fmt.Errorf("PROVIDER BUG: unsupported type for %q: %T", graphMetricAttr, v)
}
}
if v, found := metricAttrs[graphMetricFunctionAttr]; found {
s := v.(string)
if s != "" {
datapoint.Derive = s
} else {
datapoint.Derive = false
}
} else {
datapoint.Derive = false
}
if v, found := metricAttrs[graphMetricFormulaLegendAttr]; found {
switch u := v.(type) {
case string:
datapoint.LegendFormula = &u
case *string:
datapoint.LegendFormula = u
default:
return fmt.Errorf("PROVIDER BUG: unsupported type for %q: %T", graphMetricAttr, v)
}
}
if v, found := metricAttrs[graphMetricNameAttr]; found {
s := v.(string)
if s != "" {
datapoint.MetricName = s
}
}
if v, found := metricAttrs[graphMetricMetricTypeAttr]; found {
s := v.(string)
if s != "" {
datapoint.MetricType = s
}
}
if v, found := metricAttrs[graphMetricHumanNameAttr]; found {
s := v.(string)
if s != "" {
datapoint.Name = s
}
}
if v, found := metricAttrs[graphMetricStackAttr]; found {
var stackStr string
switch u := v.(type) {
case string:
stackStr = u
case *string:
if u != nil {
stackStr = *u
}
default:
return fmt.Errorf("PROVIDER BUG: unsupported type for %q: %T", graphMetricStackAttr, v)
}
if stackStr != "" {
u64, _ := strconv.ParseUint(stackStr, 10, 64)
u := uint(u64)
datapoint.Stack = &u
}
}
g.Datapoints = append(g.Datapoints, datapoint)
}
}
if listRaw, found := d.GetOk(graphMetricClusterAttr); found {
metricClusterList := listRaw.([]interface{})
for _, metricClusterListRaw := range metricClusterList {
metricClusterAttrs := newInterfaceMap(metricClusterListRaw.(map[string]interface{}))
metricCluster := api.GraphMetricCluster{}
if v, found := metricClusterAttrs[graphMetricClusterActiveAttr]; found {
metricCluster.Hidden = !(v.(bool))
}
if v, found := metricClusterAttrs[graphMetricClusterAggregateAttr]; found {
metricCluster.AggregateFunc = v.(string)
}
if v, found := metricClusterAttrs[graphMetricClusterAxisAttr]; found {
switch v.(string) {
case "left", "":
metricCluster.Axis = "l"
case "right":
metricCluster.Axis = "r"
default:
return fmt.Errorf("PROVIDER BUG: Unsupported axis attribute %q: %q", graphMetricClusterAxisAttr, v.(string))
}
}
if v, found := metricClusterAttrs[graphMetricClusterColorAttr]; found {
s := v.(string)
if s != "" {
metricCluster.Color = &s
}
}
if v, found := metricClusterAttrs[graphMetricFormulaAttr]; found {
switch v.(type) {
case string:
s := v.(string)
metricCluster.DataFormula = &s
case *string:
metricCluster.DataFormula = v.(*string)
default:
return fmt.Errorf("PROVIDER BUG: unsupported type for %q: %T", graphMetricFormulaAttr, v)
}
}
if v, found := metricClusterAttrs[graphMetricFormulaLegendAttr]; found {
switch v.(type) {
case string:
s := v.(string)
metricCluster.LegendFormula = &s
case *string:
metricCluster.LegendFormula = v.(*string)
default:
return fmt.Errorf("PROVIDER BUG: unsupported type for %q: %T", graphMetricFormulaLegendAttr, v)
}
}
if v, found := metricClusterAttrs[graphMetricClusterQueryAttr]; found {
s := v.(string)
if s != "" {
metricCluster.MetricCluster = s
}
}
if v, found := metricClusterAttrs[graphMetricHumanNameAttr]; found {
s := v.(string)
if s != "" {
metricCluster.Name = s
}
}
if v, found := metricClusterAttrs[graphMetricStackAttr]; found {
var stackStr string
switch u := v.(type) {
case string:
stackStr = u
case *string:
if u != nil {
stackStr = *u
}
default:
return fmt.Errorf("PROVIDER BUG: unsupported type for %q: %T", graphMetricStackAttr, v)
}
if stackStr != "" {
u64, _ := strconv.ParseUint(stackStr, 10, 64)
u := uint(u64)
metricCluster.Stack = &u
}
}
g.MetricClusters = append(g.MetricClusters, metricCluster)
}
}
if v, found := d.GetOk(graphStyleAttr); found {
switch v.(type) {
case string:
s := v.(string)
g.Style = &s
case *string:
g.Style = v.(*string)
default:
return fmt.Errorf("PROVIDER BUG: unsupported type for %q: %T", graphStyleAttr, v)
}
}
if v, found := d.GetOk(graphTagsAttr); found {
g.Tags = derefStringList(flattenSet(v.(*schema.Set)))
}
if err := g.Validate(); err != nil {
return err
}
return nil
}
func (g *circonusGraph) Create(ctxt *providerContext) error {
ng, err := ctxt.client.CreateGraph(&g.Graph)
if err != nil {
return err
}
g.CID = ng.CID
return nil
}
func (g *circonusGraph) Update(ctxt *providerContext) error {
_, err := ctxt.client.UpdateGraph(&g.Graph)
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to update graph %s: {{err}}", g.CID), err)
}
return nil
}
func (g *circonusGraph) Validate() error {
for i, datapoint := range g.Datapoints {
if *g.Style == apiGraphStyleLine && datapoint.Alpha != nil && *datapoint.Alpha != 0 {
return fmt.Errorf("%s can not be set on graphs with style %s", graphMetricAlphaAttr, apiGraphStyleLine)
}
if datapoint.CheckID != 0 && datapoint.MetricName == "" {
return fmt.Errorf("Error with %s[%d] name=%q: %s is set, missing attribute %s must also be set", graphMetricAttr, i, datapoint.Name, graphMetricCheckAttr, graphMetricNameAttr)
}
if datapoint.CheckID == 0 && datapoint.MetricName != "" {
return fmt.Errorf("Error with %s[%d] name=%q: %s is set, missing attribute %s must also be set", graphMetricAttr, i, datapoint.Name, graphMetricNameAttr, graphMetricCheckAttr)
}
if datapoint.CAQL != nil && (datapoint.CheckID != 0 || datapoint.MetricName != "") {
return fmt.Errorf("Error with %s[%d] name=%q: %q attribute is mutually exclusive with attributes %s or %s", graphMetricAttr, i, datapoint.Name, graphMetricCAQLAttr, graphMetricNameAttr, graphMetricCheckAttr)
}
}
for i, mc := range g.MetricClusters {
if mc.AggregateFunc != "" && (mc.Color == nil || *mc.Color == "") {
return fmt.Errorf("Error with %s[%d] name=%q: %s is a required attribute for graphs with %s set", graphMetricClusterAttr, i, mc.Name, graphMetricClusterColorAttr, graphMetricClusterAggregateAttr)
}
}
return nil
}

View File

@ -0,0 +1,199 @@
package circonus
import (
"fmt"
"strings"
"testing"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccCirconusGraph_basic(t *testing.T) {
graphName := fmt.Sprintf("Test Graph - %s", acctest.RandString(5))
checkName := fmt.Sprintf("ICMP Ping check - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusGraph,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusGraphConfigFmt, checkName, graphName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "name", graphName),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "description", "Terraform Test: mixed graph"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "notes", "test notes"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "graph_style", "line"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "left.%", "1"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "left.max", "11"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "right.%", "3"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "right.logarithmic", "10"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "right.max", "20"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "right.min", "-1"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "line_style", "stepped"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.#", "2"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.0.caql", ""),
resource.TestCheckResourceAttrSet("circonus_graph.mixed-points", "metric.0.check"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.0.metric_name", "maximum"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.0.metric_type", "numeric"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.0.name", "Maximum Latency"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.0.axis", "left"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.0.color", "#657aa6"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.0.function", "gauge"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.0.active", "true"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.1.caql", ""),
resource.TestCheckResourceAttrSet("circonus_graph.mixed-points", "metric.1.check"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.1.metric_name", "minimum"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.1.metric_type", "numeric"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.1.name", "Minimum Latency"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.1.axis", "right"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.1.color", "#657aa6"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.1.function", "gauge"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "metric.1.active", "true"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_graph.mixed-points", "tags.1401442048", "lifecycle:unittest"),
),
},
},
})
}
func testAccCheckDestroyCirconusGraph(s *terraform.State) error {
ctxt := testAccProvider.Meta().(*providerContext)
for _, rs := range s.RootModule().Resources {
if rs.Type != "circonus_graph" {
continue
}
cid := rs.Primary.ID
exists, err := checkGraphExists(ctxt, api.CIDType(&cid))
switch {
case !exists:
// noop
case exists:
return fmt.Errorf("graph still exists after destroy")
case err != nil:
return fmt.Errorf("Error checking graph %s", err)
}
}
return nil
}
func checkGraphExists(c *providerContext, graphID api.CIDType) (bool, error) {
g, err := c.client.FetchGraph(graphID)
if err != nil {
if strings.Contains(err.Error(), defaultCirconus404ErrorString) {
return false, nil
}
return false, err
}
if api.CIDType(&g.CID) == graphID {
return true, nil
}
return false, nil
}
const testAccCirconusGraphConfigFmt = `
variable "test_tags" {
type = "list"
default = [ "author:terraform", "lifecycle:unittest" ]
}
resource "circonus_check" "api_latency" {
active = true
name = "%s"
period = "60s"
collector {
id = "/broker/1"
}
icmp_ping {
count = 5
}
metric {
name = "maximum"
tags = [ "${var.test_tags}" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "minimum"
tags = [ "${var.test_tags}" ]
type = "numeric"
unit = "seconds"
}
tags = [ "${var.test_tags}" ]
target = "api.circonus.com"
}
resource "circonus_graph" "mixed-points" {
name = "%s"
description = "Terraform Test: mixed graph"
notes = "test notes"
graph_style = "line"
line_style = "stepped"
metric {
# caql = "" # conflicts with metric_name/check
check = "${circonus_check.api_latency.checks[0]}"
metric_name = "maximum"
metric_type = "numeric"
name = "Maximum Latency"
axis = "left" # right
color = "#657aa6"
function = "gauge"
active = true
}
metric {
# caql = "" # conflicts with metric_name/check
check = "${circonus_check.api_latency.checks[0]}"
metric_name = "minimum"
metric_type = "numeric"
name = "Minimum Latency"
axis = "right" # left
color = "#657aa6"
function = "gauge"
active = true
}
// metric_cluster {
// active = true
// aggregate = "average"
// axis = "left" # right
// color = "#657aa6"
// group = "${circonus_check.api_latency.checks[0]}"
// name = "Metrics Used"
// }
left {
max = 11
}
right {
logarithmic = 10
max = 20
min = -1
}
tags = [ "${var.test_tags}" ]
}
`

View File

@ -0,0 +1,138 @@
package circonus
// The `circonus_metric` type is a synthetic, top-level resource that doesn't
// actually exist within Circonus. The `circonus_check` resource uses
// `circonus_metric` as input to its `metric` attribute. The `circonus_check`
// resource can, if configured, override various parameters in the
// `circonus_metric` resource if no value was set (e.g. the `icmp_ping` will
// implicitly set the `unit` metric to `seconds`).
import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)
const (
// circonus_metric.* resource attribute names
metricActiveAttr = "active"
metricIDAttr = "id"
metricNameAttr = "name"
metricTypeAttr = "type"
metricTagsAttr = "tags"
metricUnitAttr = "unit"
// CheckBundle.Metric.Status can be one of these values
metricStatusActive = "active"
metricStatusAvailable = "available"
)
var metricDescriptions = attrDescrs{
metricActiveAttr: "Enables or disables the metric",
metricNameAttr: "Name of the metric",
metricTypeAttr: "Type of metric (e.g. numeric, histogram, text)",
metricTagsAttr: "Tags assigned to the metric",
metricUnitAttr: "The unit of measurement for a metric",
}
func resourceMetric() *schema.Resource {
return &schema.Resource{
Create: metricCreate,
Read: metricRead,
Update: metricUpdate,
Delete: metricDelete,
Exists: metricExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: convertToHelperSchema(metricDescriptions, map[schemaAttr]*schema.Schema{
metricActiveAttr: &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
metricNameAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRegexp(metricNameAttr, `[\S]+`),
},
metricTypeAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateStringIn(metricTypeAttr, validMetricTypes),
},
metricTagsAttr: tagMakeConfigSchema(metricTagsAttr),
metricUnitAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: metricUnit,
ValidateFunc: validateRegexp(metricUnitAttr, metricUnitRegexp),
},
}),
}
}
func metricCreate(d *schema.ResourceData, meta interface{}) error {
m := newMetric()
id := d.Id()
if id == "" {
var err error
id, err = newMetricID()
if err != nil {
return errwrap.Wrapf("metric ID creation failed: {{err}}", err)
}
}
if err := m.ParseConfig(id, d); err != nil {
return errwrap.Wrapf("error parsing metric schema during create: {{err}}", err)
}
if err := m.Create(d); err != nil {
return errwrap.Wrapf("error creating metric: {{err}}", err)
}
return metricRead(d, meta)
}
func metricRead(d *schema.ResourceData, meta interface{}) error {
m := newMetric()
if err := m.ParseConfig(d.Id(), d); err != nil {
return errwrap.Wrapf("error parsing metric schema during read: {{err}}", err)
}
if err := m.SaveState(d); err != nil {
return errwrap.Wrapf("error saving metric during read: {{err}}", err)
}
return nil
}
func metricUpdate(d *schema.ResourceData, meta interface{}) error {
m := newMetric()
if err := m.ParseConfig(d.Id(), d); err != nil {
return errwrap.Wrapf("error parsing metric schema during update: {{err}}", err)
}
if err := m.Update(d); err != nil {
return errwrap.Wrapf("error updating metric: {{err}}", err)
}
return nil
}
func metricDelete(d *schema.ResourceData, meta interface{}) error {
d.SetId("")
return nil
}
func metricExists(d *schema.ResourceData, meta interface{}) (bool, error) {
if id := d.Id(); id != "" {
return true, nil
}
return false, nil
}

View File

@ -0,0 +1,262 @@
package circonus
import (
"bytes"
"fmt"
"strings"
"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_metric_cluster.* resource attribute names
metricClusterDescriptionAttr = "description"
metricClusterNameAttr = "name"
metricClusterQueryAttr = "query"
metricClusterTagsAttr = "tags"
// circonus_metric_cluster.* out parameters
metricClusterIDAttr = "id"
// circonus_metric_cluster.query.* resource attribute names
metricClusterDefinitionAttr = "definition"
metricClusterTypeAttr = "type"
)
var metricClusterDescriptions = attrDescrs{
metricClusterDescriptionAttr: "A description of the metric cluster",
metricClusterIDAttr: "The ID of this metric cluster",
metricClusterNameAttr: "The name of the metric cluster",
metricClusterQueryAttr: "A metric cluster query definition",
metricClusterTagsAttr: "A list of tags assigned to the metric cluster",
}
var metricClusterQueryDescriptions = attrDescrs{
metricClusterDefinitionAttr: "A query to select a collection of metric streams",
metricClusterTypeAttr: "The operation to perform on the matching metric streams",
}
func resourceMetricCluster() *schema.Resource {
return &schema.Resource{
Create: metricClusterCreate,
Read: metricClusterRead,
Update: metricClusterUpdate,
Delete: metricClusterDelete,
Exists: metricClusterExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: convertToHelperSchema(metricClusterDescriptions, map[schemaAttr]*schema.Schema{
metricClusterDescriptionAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
StateFunc: suppressWhitespace,
},
metricClusterNameAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
},
metricClusterQueryAttr: &schema.Schema{
Type: schema.TypeSet,
Optional: true,
MinItems: 1,
Elem: &schema.Resource{
Schema: convertToHelperSchema(metricClusterQueryDescriptions, map[schemaAttr]*schema.Schema{
metricClusterDefinitionAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRegexp(metricClusterDefinitionAttr, `.+`),
},
metricClusterTypeAttr: &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateStringIn(metricClusterTypeAttr, supportedMetricClusterTypes),
},
}),
},
},
metricClusterTagsAttr: tagMakeConfigSchema(metricClusterTagsAttr),
// Out parameters
metricClusterIDAttr: &schema.Schema{
Computed: true,
Type: schema.TypeString,
ValidateFunc: validateRegexp(metricClusterIDAttr, config.MetricClusterCIDRegex),
},
}),
}
}
func metricClusterCreate(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
mc := newMetricCluster()
if err := mc.ParseConfig(d); err != nil {
return errwrap.Wrapf("error parsing metric cluster schema during create: {{err}}", err)
}
if err := mc.Create(ctxt); err != nil {
return errwrap.Wrapf("error creating metric cluster: {{err}}", err)
}
d.SetId(mc.CID)
return metricClusterRead(d, meta)
}
func metricClusterExists(d *schema.ResourceData, meta interface{}) (bool, error) {
ctxt := meta.(*providerContext)
cid := d.Id()
mc, err := ctxt.client.FetchMetricCluster(api.CIDType(&cid), "")
if err != nil {
if strings.Contains(err.Error(), defaultCirconus404ErrorString) {
return false, nil
}
return false, err
}
if mc.CID == "" {
return false, nil
}
return true, nil
}
// metricClusterRead pulls data out of the MetricCluster object and stores it
// into the appropriate place in the statefile.
func metricClusterRead(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
cid := d.Id()
mc, err := loadMetricCluster(ctxt, api.CIDType(&cid))
if err != nil {
return err
}
d.SetId(mc.CID)
queries := schema.NewSet(metricClusterQueryChecksum, nil)
for _, query := range mc.Queries {
queryAttrs := map[string]interface{}{
string(metricClusterDefinitionAttr): query.Query,
string(metricClusterTypeAttr): query.Type,
}
queries.Add(queryAttrs)
}
d.Set(metricClusterDescriptionAttr, mc.Description)
d.Set(metricClusterNameAttr, mc.Name)
if err := d.Set(metricClusterTagsAttr, tagsToState(apiToTags(mc.Tags))); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store metric cluster %q attribute: {{err}}", metricClusterTagsAttr), err)
}
d.Set(metricClusterIDAttr, mc.CID)
return nil
}
func metricClusterUpdate(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
mc := newMetricCluster()
if err := mc.ParseConfig(d); err != nil {
return err
}
mc.CID = d.Id()
if err := mc.Update(ctxt); err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to update metric cluster %q: {{err}}", d.Id()), err)
}
return metricClusterRead(d, meta)
}
func metricClusterDelete(d *schema.ResourceData, meta interface{}) error {
ctxt := meta.(*providerContext)
cid := d.Id()
if _, err := ctxt.client.DeleteMetricClusterByCID(api.CIDType(&cid)); err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to delete metric cluster %q: {{err}}", d.Id()), err)
}
d.SetId("")
return nil
}
func metricClusterQueryChecksum(v interface{}) int {
m := v.(map[string]interface{})
b := &bytes.Buffer{}
b.Grow(defaultHashBufSize)
// Order writes to the buffer using lexically sorted list for easy visual
// reconciliation with other lists.
if v, found := m[metricClusterDefinitionAttr]; found {
fmt.Fprint(b, v.(string))
}
if v, found := m[metricClusterTypeAttr]; found {
fmt.Fprint(b, v.(string))
}
s := b.String()
return hashcode.String(s)
}
// ParseConfig reads Terraform config data and stores the information into a
// Circonus MetricCluster object.
func (mc *circonusMetricCluster) ParseConfig(d *schema.ResourceData) error {
if v, found := d.GetOk(metricClusterDescriptionAttr); found {
mc.Description = v.(string)
}
if v, found := d.GetOk(metricClusterNameAttr); found {
mc.Name = v.(string)
}
if queryListRaw, found := d.GetOk(metricClusterQueryAttr); found {
queryList := queryListRaw.(*schema.Set).List()
mc.Queries = make([]api.MetricQuery, 0, len(queryList))
for _, queryRaw := range queryList {
queryAttrs := newInterfaceMap(queryRaw)
var query string
if v, found := queryAttrs[metricClusterDefinitionAttr]; found {
query = v.(string)
}
var queryType string
if v, found := queryAttrs[metricClusterTypeAttr]; found {
queryType = v.(string)
}
mc.Queries = append(mc.Queries, api.MetricQuery{
Query: query,
Type: queryType,
})
}
}
if v, found := d.GetOk(metricClusterTagsAttr); found {
mc.Tags = derefStringList(flattenSet(v.(*schema.Set)))
}
if err := mc.Validate(); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,95 @@
package circonus
import (
"fmt"
"strings"
"testing"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccCirconusMetricCluster_basic(t *testing.T) {
metricClusterName := fmt.Sprintf("job1-stream-agg - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusMetricCluster,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusMetricClusterConfigFmt, metricClusterName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_metric_cluster.nomad-job1", "description", `Metric Cluster Description`),
resource.TestCheckResourceAttrSet("circonus_metric_cluster.nomad-job1", "id"),
resource.TestCheckResourceAttr("circonus_metric_cluster.nomad-job1", "name", metricClusterName),
resource.TestCheckResourceAttr("circonus_metric_cluster.nomad-job1", "query.236803225.definition", "*`nomad-jobname`memory`rss"),
resource.TestCheckResourceAttr("circonus_metric_cluster.nomad-job1", "query.236803225.type", "average"),
resource.TestCheckResourceAttr("circonus_metric_cluster.nomad-job1", "tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_metric_cluster.nomad-job1", "tags.3354173695", "source:nomad"),
),
},
},
})
}
func testAccCheckDestroyCirconusMetricCluster(s *terraform.State) error {
ctxt := testAccProvider.Meta().(*providerContext)
for _, rs := range s.RootModule().Resources {
if rs.Type != "circonus_metric_cluster" {
continue
}
cid := rs.Primary.ID
exists, err := checkMetricClusterExists(ctxt, api.CIDType(&cid))
switch {
case !exists:
// noop
case exists:
return fmt.Errorf("metric cluster still exists after destroy")
case err != nil:
return fmt.Errorf("Error checking metric cluster: %v", err)
}
}
return nil
}
func checkMetricClusterExists(c *providerContext, metricClusterCID api.CIDType) (bool, error) {
cmc, err := c.client.FetchMetricCluster(metricClusterCID, "")
if err != nil {
if strings.Contains(err.Error(), defaultCirconus404ErrorString) {
return false, nil
}
return false, err
}
if api.CIDType(&cmc.CID) == metricClusterCID {
return true, nil
}
return false, nil
}
const testAccCirconusMetricClusterConfigFmt = `
resource "circonus_metric_cluster" "nomad-job1" {
description = <<EOF
Metric Cluster Description
EOF
name = "%s"
query {
definition = "*` + "`" + `nomad-jobname` + "`" + `memory` + "`" + `rss"
type = "average"
}
tags = [
"author:terraform",
"source:nomad",
]
}
`

View File

@ -0,0 +1,252 @@
package circonus
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccCirconusMetric_basic(t *testing.T) {
metricAvgName := fmt.Sprintf("Average Ping Time - %s", acctest.RandString(5))
metricMaxName := fmt.Sprintf("Maximum Ping Time - %s", acctest.RandString(5))
metricMinName := fmt.Sprintf("Minimum Ping Time - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyMetric,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusMetricConfigFmt, metricAvgName, metricMaxName, metricMinName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_average", "name", metricAvgName),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_average", "active", "false"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_average", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_average", "tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_average", "tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_average", "type", "numeric"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_average", "unit", "seconds"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_maximum", "name", metricMaxName),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_maximum", "active", "true"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_maximum", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_maximum", "tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_maximum", "tags.3241999189", "source:circonus"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_maximum", "type", "numeric"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_maximum", "unit", "seconds"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_minimum", "name", metricMinName),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_minimum", "active", "true"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_minimum", "tags.#", "0"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_minimum", "type", "numeric"),
resource.TestCheckResourceAttr("circonus_metric.icmp_ping_minimum", "unit", ""),
),
},
},
})
}
func TestAccCirconusMetric_tagsets(t *testing.T) {
metricName := fmt.Sprintf("foo - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyMetric,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusMetricTagsFmt0, metricName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_metric.t", "name", metricName),
resource.TestCheckResourceAttr("circonus_metric.t", "type", "numeric"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.#", "0"),
),
},
{
Config: fmt.Sprintf(testAccCirconusMetricTagsFmt1, metricName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_metric.t", "name", metricName),
resource.TestCheckResourceAttr("circonus_metric.t", "type", "numeric"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.#", "1"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.1750285118", "foo:bar"),
),
},
{
Config: fmt.Sprintf(testAccCirconusMetricTagsFmt2, metricName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_metric.t", "name", metricName),
resource.TestCheckResourceAttr("circonus_metric.t", "type", "numeric"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.1750285118", "foo:bar"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.2693443894", "foo:baz"),
),
},
{
Config: fmt.Sprintf(testAccCirconusMetricTagsFmt3, metricName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_metric.t", "name", metricName),
resource.TestCheckResourceAttr("circonus_metric.t", "type", "numeric"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.#", "3"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.1750285118", "foo:bar"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.2693443894", "foo:baz"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.1937518738", "foo:bur"),
),
},
{
Config: fmt.Sprintf(testAccCirconusMetricTagsFmt4, metricName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_metric.t", "name", metricName),
resource.TestCheckResourceAttr("circonus_metric.t", "type", "numeric"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.#", "4"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.1750285118", "foo:bar"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.2693443894", "foo:baz"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.1937518738", "foo:bur"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.2110890696", "foo:baz2"),
),
},
{
Config: fmt.Sprintf(testAccCirconusMetricTagsFmt5, metricName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_metric.t", "name", metricName),
resource.TestCheckResourceAttr("circonus_metric.t", "type", "numeric"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.#", "3"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.1750285118", "foo:bar"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.1937518738", "foo:bur"),
resource.TestCheckResourceAttr("circonus_metric.t", "tags.2110890696", "foo:baz2"),
),
},
},
})
}
func testAccCheckDestroyMetric(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "circonus_metric" {
continue
}
id := rs.Primary.ID
exists := id == ""
switch {
case !exists:
// noop
case exists:
return fmt.Errorf("metric still exists after destroy")
}
}
return nil
}
const testAccCirconusMetricConfigFmt = `
resource "circonus_metric" "icmp_ping_average" {
name = "%s"
active = false
type = "numeric"
unit = "seconds"
tags = [
"author:terraform",
"source:circonus",
]
}
resource "circonus_metric" "icmp_ping_maximum" {
name = "%s"
active = true
type = "numeric"
unit = "seconds"
tags = [
"source:circonus",
"author:terraform",
]
}
resource "circonus_metric" "icmp_ping_minimum" {
name = "%s"
type = "numeric"
}
`
const testAccCirconusMetricTagsFmt0 = `
resource "circonus_metric" "t" {
name = "%s"
# tags = [
# "foo:bar",
# "foo:baz",
# "foo:bur",
# "foo:baz2"
# ]
type = "numeric"
}
`
const testAccCirconusMetricTagsFmt1 = `
resource "circonus_metric" "t" {
name = "%s"
tags = [
"foo:bar",
# "foo:baz",
# "foo:bur",
# "foo:baz2"
]
type = "numeric"
}
`
const testAccCirconusMetricTagsFmt2 = `
resource "circonus_metric" "t" {
name = "%s"
tags = [
"foo:bar",
"foo:baz",
# "foo:bur",
# "foo:baz2"
]
type = "numeric"
}
`
const testAccCirconusMetricTagsFmt3 = `
resource "circonus_metric" "t" {
name = "%s"
tags = [
"foo:bar",
"foo:baz",
"foo:bur",
# "foo:baz2"
]
type = "numeric"
}
`
const testAccCirconusMetricTagsFmt4 = `
resource "circonus_metric" "t" {
name = "%s"
tags = [
"foo:bar",
"foo:baz",
"foo:bur",
"foo:baz2"
]
type = "numeric"
}
`
const testAccCirconusMetricTagsFmt5 = `
resource "circonus_metric" "t" {
name = "%s"
tags = [
"foo:bar",
# "foo:baz",
"foo:bur",
"foo:baz2"
]
type = "numeric"
}
`

View File

@ -0,0 +1,866 @@
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),
},
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(ruleSetNotMatchAttr)] = rule.Value
case apiRuleSetNotMatch:
valueAttrs[string(ruleSetNotContainAttr)] = 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
}

View File

@ -0,0 +1,226 @@
package circonus
import (
"fmt"
"strings"
"testing"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccCirconusRuleSet_basic(t *testing.T) {
checkName := fmt.Sprintf("ICMP Ping check - %s", acctest.RandString(5))
contactGroupName := fmt.Sprintf("ops-staging-sev3 - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusRuleSet,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusRuleSetConfigFmt, contactGroupName, checkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("circonus_rule_set.icmp-latency-alarm", "check"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "metric_name", "maximum"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "metric_type", "numeric"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "notes", "Simple check to create notifications based on ICMP performance."),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "link", "https://wiki.example.org/playbook/what-to-do-when-high-latency-strikes"),
// resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "parent", "some check ID"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.#", "4"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.0.value.#", "1"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.0.value.360613670.absent", "70s"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.0.value.360613670.over.#", "0"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.0.then.#", "1"),
// Computed:
// resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.0.then.<computed>.notify.#", "1"),
// resource.TestCheckResourceAttrSet("circonus_rule_set.icmp-latency-alarm", "if.0.then.<computed>.notify.0"),
// resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.0.then.<computed>.severity", "1"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.1.value.#", "1"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.1.value.2300199732.over.#", "1"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.1.value.2300199732.over.689776960.last", "120s"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.1.value.2300199732.over.689776960.using", "average"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.1.value.2300199732.min_value", "2"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.1.then.#", "1"),
// Computed:
// resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.1.then.<computed>.notify.#", "1"),
// resource.TestCheckResourceAttrSet("circonus_rule_set.icmp-latency-alarm", "if.1.then.<computed>.notify.0"),
// resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.1.then.<computed>.severity", "2"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.2.value.#", "1"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.2.value.2842654150.over.#", "1"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.2.value.2842654150.over.999877839.last", "180s"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.2.value.2842654150.over.999877839.using", "average"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.2.value.2842654150.max_value", "300"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.2.then.#", "1"),
// Computed:
// resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.2.then.<computed>.notify.#", "1"),
// resource.TestCheckResourceAttrSet("circonus_rule_set.icmp-latency-alarm", "if.2.then.<computed>.notify.0"),
// resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.2.then.<computed>.severity", "3"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.3.value.#", "1"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.3.value.803690187.over.#", "0"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.3.value.803690187.max_value", "400"),
// Computed:
// resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.3.then.<computed>.notify.#", "1"),
// resource.TestCheckResourceAttrSet("circonus_rule_set.icmp-latency-alarm", "if.3.then.<computed>.notify.0"),
// resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.3.then.<computed>.after", "2400s"),
// resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "if.3.then.<computed>.severity", "4"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "tags.2087084518", "author:terraform"),
resource.TestCheckResourceAttr("circonus_rule_set.icmp-latency-alarm", "tags.1401442048", "lifecycle:unittest"),
),
},
},
})
}
func testAccCheckDestroyCirconusRuleSet(s *terraform.State) error {
ctxt := testAccProvider.Meta().(*providerContext)
for _, rs := range s.RootModule().Resources {
if rs.Type != "circonus_rule_set" {
continue
}
cid := rs.Primary.ID
exists, err := checkRuleSetExists(ctxt, api.CIDType(&cid))
switch {
case !exists:
// noop
case exists:
return fmt.Errorf("rule set still exists after destroy")
case err != nil:
return fmt.Errorf("Error checking rule set: %v", err)
}
}
return nil
}
func checkRuleSetExists(c *providerContext, ruleSetCID api.CIDType) (bool, error) {
rs, err := c.client.FetchRuleSet(ruleSetCID)
if err != nil {
if strings.Contains(err.Error(), defaultCirconus404ErrorString) {
return false, nil
}
return false, err
}
if api.CIDType(&rs.CID) == ruleSetCID {
return true, nil
}
return false, nil
}
const testAccCirconusRuleSetConfigFmt = `
variable "test_tags" {
type = "list"
default = [ "author:terraform", "lifecycle:unittest" ]
}
resource "circonus_contact_group" "test-trigger" {
name = "%s"
tags = [ "${var.test_tags}" ]
}
resource "circonus_check" "api_latency" {
active = true
name = "%s"
period = "60s"
collector {
id = "/broker/1"
}
icmp_ping {
count = 1
}
metric {
name = "maximum"
tags = [ "${var.test_tags}" ]
type = "numeric"
unit = "seconds"
}
tags = [ "${var.test_tags}" ]
target = "api.circonus.com"
}
resource "circonus_rule_set" "icmp-latency-alarm" {
check = "${circonus_check.api_latency.checks[0]}"
metric_name = "maximum"
// metric_name = "${circonus_check.api_latency.metric["maximum"].name}"
// metric_type = "${circonus_check.api_latency.metric["maximum"].type}"
notes = <<EOF
Simple check to create notifications based on ICMP performance.
EOF
link = "https://wiki.example.org/playbook/what-to-do-when-high-latency-strikes"
# parent = "${check cid}"
if {
value {
absent = "70s"
}
then {
notify = [ "${circonus_contact_group.test-trigger.id}" ]
severity = 1
}
}
if {
value {
over {
last = "120s"
using = "average"
}
min_value = 2
}
then {
notify = [ "${circonus_contact_group.test-trigger.id}" ]
severity = 2
}
}
if {
value {
over {
last = "180s"
using = "average"
}
max_value = 300
}
then {
notify = [ "${circonus_contact_group.test-trigger.id}" ]
severity = 3
}
}
if {
value {
max_value = 400
}
then {
notify = [ "${circonus_contact_group.test-trigger.id}" ]
after = "2400s"
severity = 4
}
}
tags = [ "${var.test_tags}" ]
}
`

View File

@ -0,0 +1,65 @@
package circonus
import (
"log"
"strings"
"github.com/hashicorp/terraform/helper/schema"
)
type circonusTag string
type circonusTags []circonusTag
// tagMakeConfigSchema returns a schema pointer to the necessary tag structure.
func tagMakeConfigSchema(tagAttrName schemaAttr) *schema.Schema {
return &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateTag,
},
}
}
func (t circonusTag) Category() string {
tagInfo := strings.SplitN(string(t), ":", 2)
switch len(tagInfo) {
case 1:
return string(t)
case 2:
return tagInfo[0]
default:
log.Printf("[ERROR]: Invalid category on tag %q", string(t))
return ""
}
}
func (t circonusTag) Value() string {
tagInfo := strings.SplitN(string(t), ":", 2)
switch len(tagInfo) {
case 1:
return ""
case 2:
return tagInfo[1]
default:
log.Printf("[ERROR]: Invalid value on tag %q", string(t))
return ""
}
}
func tagsToState(tags circonusTags) *schema.Set {
tagSet := schema.NewSet(schema.HashString, nil)
for i := range tags {
tagSet.Add(string(tags[i]))
}
return tagSet
}
func apiToTags(apiTags []string) circonusTags {
tags := make(circonusTags, 0, len(apiTags))
for _, v := range apiTags {
tags = append(tags, circonusTag(v))
}
return tags
}

View File

@ -0,0 +1,21 @@
package circonus
// NOTE(sean): One of the objectives of the use of types is to ensure that based
// on aesthetics alone are very few locations where type assertions or casting
// in the main resource files is required (mainly when interacting with the
// external API structs). As a rule of thumb, all type assertions should happen
// in the utils file and casting is only done at assignment time when storing a
// result to a struct. Said differently, contained tedium should enable
// compiler enforcement of types and easy verification.
type apiCheckType string
type attrDescr string
type attrDescrs map[schemaAttr]attrDescr
type schemaAttr string
type metricID string
type validString string
type validStringValues []validString

View File

@ -0,0 +1,149 @@
package circonus
import (
"fmt"
"log"
"regexp"
"strconv"
"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/schema"
)
// convertToHelperSchema converts the schema and injects the necessary
// parameters, notably the descriptions, in order to be valid input to
// Terraform's helper schema.
func convertToHelperSchema(descrs attrDescrs, in map[schemaAttr]*schema.Schema) map[string]*schema.Schema {
out := make(map[string]*schema.Schema, len(in))
for k, v := range in {
if descr, ok := descrs[k]; ok {
// NOTE(sean@): At some point this check needs to be uncommented and all
// empty descriptions need to be populated.
//
// if len(descr) == 0 {
// log.Printf("[WARN] PROVIDER BUG: Description of attribute %s empty", k)
// }
v.Description = string(descr)
} else {
log.Printf("[WARN] PROVIDER BUG: Unable to find description for attr %q", k)
}
out[string(k)] = v
}
return out
}
func failoverGroupIDToCID(groupID int) string {
if groupID == 0 {
return ""
}
return fmt.Sprintf("%s/%d", config.ContactGroupPrefix, groupID)
}
func failoverGroupCIDToID(cid api.CIDType) (int, error) {
re := regexp.MustCompile("^" + config.ContactGroupPrefix + "/(" + config.DefaultCIDRegex + ")$")
matches := re.FindStringSubmatch(string(*cid))
if matches == nil || len(matches) < 2 {
return -1, fmt.Errorf("Did not find a valid contact_group ID in the CID %q", string(*cid))
}
contactGroupID, err := strconv.Atoi(matches[1])
if err != nil {
return -1, errwrap.Wrapf(fmt.Sprintf("invalid contact_group ID: unable to find an ID in %q: {{error}}", string(*cid)), err)
}
return contactGroupID, nil
}
// flattenList returns a list of all string values to a []*string.
func flattenList(l []interface{}) []*string {
vals := make([]*string, 0, len(l))
for _, v := range l {
val, ok := v.(string)
if ok && val != "" {
vals = append(vals, &val)
}
}
return vals
}
// flattenSet flattens the values in a schema.Set and returns a []*string
func flattenSet(s *schema.Set) []*string {
return flattenList(s.List())
}
func derefStringList(lp []*string) []string {
l := make([]string, 0, len(lp))
for _, sp := range lp {
if sp != nil {
l = append(l, *sp)
}
}
return l
}
// listToSet returns a TypeSet from the given list.
func stringListToSet(stringList []string, keyName schemaAttr) []interface{} {
m := make([]interface{}, 0, len(stringList))
for _, v := range stringList {
s := make(map[string]interface{}, 1)
s[string(keyName)] = v
m = append(m, s)
}
return m
}
func normalizeTimeDurationStringToSeconds(v interface{}) string {
switch v.(type) {
case string:
d, err := time.ParseDuration(v.(string))
if err != nil {
return fmt.Sprintf("<unable to normalize time duration %s: %v>", v.(string), err)
}
return fmt.Sprintf("%ds", int(d.Seconds()))
default:
return fmt.Sprintf("<unable to normalize duration on %#v>", v)
}
}
func indirect(v interface{}) interface{} {
switch v.(type) {
case string:
return v
case *string:
p := v.(*string)
if p == nil {
return nil
}
return *p
default:
return v
}
}
func suppressEquivalentTimeDurations(k, old, new string, d *schema.ResourceData) bool {
d1, err := time.ParseDuration(old)
if err != nil {
return false
}
d2, err := time.ParseDuration(new)
if err != nil {
return false
}
return d1 == d2
}
func suppressWhitespace(v interface{}) string {
return strings.TrimSpace(v.(string))
}

View File

@ -0,0 +1,370 @@
package circonus
import (
"fmt"
"net/url"
"regexp"
"strings"
"time"
"github.com/circonus-labs/circonus-gometrics/api"
"github.com/circonus-labs/circonus-gometrics/api/config"
"github.com/hashicorp/errwrap"
)
var knownCheckTypes map[circonusCheckType]struct{}
var knownContactMethods map[contactMethods]struct{}
var userContactMethods map[contactMethods]struct{}
var externalContactMethods map[contactMethods]struct{}
var supportedHTTPVersions = validStringValues{"0.9", "1.0", "1.1", "2.0"}
var supportedMetricClusterTypes = validStringValues{
"average", "count", "counter", "counter2", "counter2_stddev",
"counter_stddev", "derive", "derive2", "derive2_stddev", "derive_stddev",
"histogram", "stddev", "text",
}
func init() {
checkTypes := []circonusCheckType{
"caql", "cim", "circonuswindowsagent", "circonuswindowsagent,nad",
"collectd", "composite", "dcm", "dhcp", "dns", "elasticsearch",
"external", "ganglia", "googleanalytics", "haproxy", "http",
"http,apache", "httptrap", "imap", "jmx", "json", "json,couchdb",
"json,mongodb", "json,nad", "json,riak", "ldap", "memcached",
"munin", "mysql", "newrelic_rpm", "nginx", "nrpe", "ntp",
"oracle", "ping_icmp", "pop3", "postgres", "redis", "resmon",
"smtp", "snmp", "snmp,momentum", "sqlserver", "ssh2", "statsd",
"tcp", "varnish", "keynote", "keynote_pulse", "cloudwatch",
"ec_console", "mongodb",
}
knownCheckTypes = make(map[circonusCheckType]struct{}, len(checkTypes))
for _, k := range checkTypes {
knownCheckTypes[k] = struct{}{}
}
userMethods := []contactMethods{"email", "sms", "xmpp"}
externalMethods := []contactMethods{"slack"}
knownContactMethods = make(map[contactMethods]struct{}, len(externalContactMethods)+len(userContactMethods))
externalContactMethods = make(map[contactMethods]struct{}, len(externalMethods))
for _, k := range externalMethods {
knownContactMethods[k] = struct{}{}
externalContactMethods[k] = struct{}{}
}
userContactMethods = make(map[contactMethods]struct{}, len(userMethods))
for _, k := range userMethods {
knownContactMethods[k] = struct{}{}
userContactMethods[k] = struct{}{}
}
}
func validateCheckType(v interface{}, key string) (warnings []string, errors []error) {
if _, ok := knownCheckTypes[circonusCheckType(v.(string))]; !ok {
warnings = append(warnings, fmt.Sprintf("Possibly unsupported check type: %s", v.(string)))
}
return warnings, errors
}
func validateCheckCloudWatchDimmensions(v interface{}, key string) (warnings []string, errors []error) {
validDimmensionName := regexp.MustCompile(`^[\S]+$`)
validDimmensionValue := regexp.MustCompile(`^[\S]+$`)
dimmensions := v.(map[string]interface{})
for k, vRaw := range dimmensions {
if !validDimmensionName.MatchString(k) {
errors = append(errors, fmt.Errorf("Invalid CloudWatch Dimmension Name specified: %q", k))
continue
}
v := vRaw.(string)
if !validDimmensionValue.MatchString(v) {
errors = append(errors, fmt.Errorf("Invalid value for CloudWatch Dimmension %q specified: %q", k, v))
}
}
return warnings, errors
}
func validateContactGroup(cg *api.ContactGroup) error {
for i := range cg.Reminders {
if cg.Reminders[i] != 0 && cg.AggregationWindow > cg.Reminders[i] {
return fmt.Errorf("severity %d reminder (%ds) is shorter than the aggregation window (%ds)", i+1, cg.Reminders[i], cg.AggregationWindow)
}
}
for severityIndex := range cg.Escalations {
switch {
case cg.Escalations[severityIndex] == nil:
continue
case cg.Escalations[severityIndex].After > 0 && cg.Escalations[severityIndex].ContactGroupCID == "",
cg.Escalations[severityIndex].After == 0 && cg.Escalations[severityIndex].ContactGroupCID != "":
return fmt.Errorf("severity %d escalation requires both and %s and %s be set", severityIndex+1, contactEscalateToAttr, contactEscalateAfterAttr)
}
}
return nil
}
func validateContactGroupCID(attrName schemaAttr) func(v interface{}, key string) (warnings []string, errors []error) {
return func(v interface{}, key string) (warnings []string, errors []error) {
validContactGroupCID := regexp.MustCompile(config.ContactGroupCIDRegex)
if !validContactGroupCID.MatchString(v.(string)) {
errors = append(errors, fmt.Errorf("Invalid %s specified (%q)", attrName, v.(string)))
}
return warnings, errors
}
}
func validateDurationMin(attrName schemaAttr, minDuration string) func(v interface{}, key string) (warnings []string, errors []error) {
var min time.Duration
{
var err error
min, err = time.ParseDuration(minDuration)
if err != nil {
return func(interface{}, string) (warnings []string, errors []error) {
errors = []error{errwrap.Wrapf(fmt.Sprintf("Invalid time +%q: {{err}}", minDuration), err)}
return warnings, errors
}
}
}
return func(v interface{}, key string) (warnings []string, errors []error) {
d, err := time.ParseDuration(v.(string))
switch {
case err != nil:
errors = append(errors, errwrap.Wrapf(fmt.Sprintf("Invalid %s specified (%q): {{err}}", attrName, v.(string)), err))
case d < min:
errors = append(errors, fmt.Errorf("Invalid %s specified (%q): minimum value must be %s", attrName, v.(string), min))
}
return warnings, errors
}
}
func validateDurationMax(attrName schemaAttr, maxDuration string) func(v interface{}, key string) (warnings []string, errors []error) {
var max time.Duration
{
var err error
max, err = time.ParseDuration(maxDuration)
if err != nil {
return func(interface{}, string) (warnings []string, errors []error) {
errors = []error{errwrap.Wrapf(fmt.Sprintf("Invalid time +%q: {{err}}", maxDuration), err)}
return warnings, errors
}
}
}
return func(v interface{}, key string) (warnings []string, errors []error) {
d, err := time.ParseDuration(v.(string))
switch {
case err != nil:
errors = append(errors, errwrap.Wrapf(fmt.Sprintf("Invalid %s specified (%q): {{err}}", attrName, v.(string)), err))
case d > max:
errors = append(errors, fmt.Errorf("Invalid %s specified (%q): maximum value must be less than or equal to %s", attrName, v.(string), max))
}
return warnings, errors
}
}
func validateFloatMin(attrName schemaAttr, min float64) func(v interface{}, key string) (warnings []string, errors []error) {
return func(v interface{}, key string) (warnings []string, errors []error) {
if v.(float64) < min {
errors = append(errors, fmt.Errorf("Invalid %s specified (%f): minimum value must be %f", attrName, v.(float64), min))
}
return warnings, errors
}
}
func validateFloatMax(attrName schemaAttr, max float64) func(v interface{}, key string) (warnings []string, errors []error) {
return func(v interface{}, key string) (warnings []string, errors []error) {
if v.(float64) > max {
errors = append(errors, fmt.Errorf("Invalid %s specified (%f): maximum value must be %f", attrName, v.(float64), max))
}
return warnings, errors
}
}
// validateFuncs takes a list of functions and runs them in serial until either
// a warning or error is returned from the first validation function argument.
func validateFuncs(fns ...func(v interface{}, key string) (warnings []string, errors []error)) func(v interface{}, key string) (warnings []string, errors []error) {
return func(v interface{}, key string) (warnings []string, errors []error) {
for _, fn := range fns {
warnings, errors = fn(v, key)
if len(warnings) > 0 || len(errors) > 0 {
break
}
}
return warnings, errors
}
}
func validateHTTPHeaders(v interface{}, key string) (warnings []string, errors []error) {
validHTTPHeader := regexp.MustCompile(`.+`)
validHTTPValue := regexp.MustCompile(`.+`)
headers := v.(map[string]interface{})
for k, vRaw := range headers {
if !validHTTPHeader.MatchString(k) {
errors = append(errors, fmt.Errorf("Invalid HTTP Header specified: %q", k))
continue
}
v := vRaw.(string)
if !validHTTPValue.MatchString(v) {
errors = append(errors, fmt.Errorf("Invalid value for HTTP Header %q specified: %q", k, v))
}
}
return warnings, errors
}
func validateGraphAxisOptions(v interface{}, key string) (warnings []string, errors []error) {
axisOptionsMap := v.(map[string]interface{})
validOpts := map[schemaAttr]struct{}{
graphAxisLogarithmicAttr: struct{}{},
graphAxisMaxAttr: struct{}{},
graphAxisMinAttr: struct{}{},
}
for k := range axisOptionsMap {
if _, ok := validOpts[schemaAttr(k)]; !ok {
errors = append(errors, fmt.Errorf("Invalid axis option specified: %q", k))
continue
}
}
return warnings, errors
}
func validateIntMin(attrName schemaAttr, min int) func(v interface{}, key string) (warnings []string, errors []error) {
return func(v interface{}, key string) (warnings []string, errors []error) {
if v.(int) < min {
errors = append(errors, fmt.Errorf("Invalid %s specified (%d): minimum value must be %d", attrName, v.(int), min))
}
return warnings, errors
}
}
func validateIntMax(attrName schemaAttr, max int) func(v interface{}, key string) (warnings []string, errors []error) {
return func(v interface{}, key string) (warnings []string, errors []error) {
if v.(int) > max {
errors = append(errors, fmt.Errorf("Invalid %s specified (%d): maximum value must be %d", attrName, v.(int), max))
}
return warnings, errors
}
}
func validateMetricType(v interface{}, key string) (warnings []string, errors []error) {
value := v.(string)
switch value {
case "caql", "composite", "histogram", "numeric", "text":
default:
errors = append(errors, fmt.Errorf("unsupported metric type %s", value))
}
return warnings, errors
}
func validateRegexp(attrName schemaAttr, reString string) func(v interface{}, key string) (warnings []string, errors []error) {
re := regexp.MustCompile(reString)
return func(v interface{}, key string) (warnings []string, errors []error) {
if !re.MatchString(v.(string)) {
errors = append(errors, fmt.Errorf("Invalid %s specified (%q): regexp failed to match string", attrName, v.(string)))
}
return warnings, errors
}
}
func validateTag(v interface{}, key string) (warnings []string, errors []error) {
tag := v.(string)
if !strings.ContainsRune(tag, ':') {
errors = append(errors, fmt.Errorf("tag %q is missing a category", tag))
}
return warnings, errors
}
func validateUserCID(attrName string) func(v interface{}, key string) (warnings []string, errors []error) {
return func(v interface{}, key string) (warnings []string, errors []error) {
valid := regexp.MustCompile(config.UserCIDRegex)
if !valid.MatchString(v.(string)) {
errors = append(errors, fmt.Errorf("Invalid %s specified (%q)", attrName, v.(string)))
}
return warnings, errors
}
}
type urlParseFlags int
const (
urlIsAbs urlParseFlags = 1 << iota
urlWithoutSchema
urlWithoutPort
)
const urlBasicCheck urlParseFlags = 0
func validateHTTPURL(attrName schemaAttr, checkFlags urlParseFlags) func(v interface{}, key string) (warnings []string, errors []error) {
return func(v interface{}, key string) (warnings []string, errors []error) {
u, err := url.Parse(v.(string))
switch {
case err != nil:
errors = append(errors, errwrap.Wrapf(fmt.Sprintf("Invalid %s specified (%q): {{err}}", attrName, v.(string)), err))
case u.Host == "":
errors = append(errors, fmt.Errorf("Invalid %s specified: host can not be empty", attrName))
case !(u.Scheme == "http" || u.Scheme == "https"):
errors = append(errors, fmt.Errorf("Invalid %s specified: scheme unsupported (only support http and https)", attrName))
}
if checkFlags&urlIsAbs != 0 && !u.IsAbs() {
errors = append(errors, fmt.Errorf("Schema is missing from URL %q (HINT: https://%s)", v.(string), v.(string)))
}
if checkFlags&urlWithoutSchema != 0 && u.IsAbs() {
errors = append(errors, fmt.Errorf("Schema is present on URL %q (HINT: drop the https://%s)", v.(string), v.(string)))
}
if checkFlags&urlWithoutPort != 0 {
hostParts := strings.SplitN(u.Host, ":", 2)
if len(hostParts) != 1 {
errors = append(errors, fmt.Errorf("Port is present on URL %q (HINT: drop the :%s)", v.(string), hostParts[1]))
}
}
return warnings, errors
}
}
func validateStringIn(attrName schemaAttr, valid validStringValues) func(v interface{}, key string) (warnings []string, errors []error) {
return func(v interface{}, key string) (warnings []string, errors []error) {
s := v.(string)
var found bool
for i := range valid {
if s == string(valid[i]) {
found = true
break
}
}
if !found {
errors = append(errors, fmt.Errorf("Invalid %q specified: %q not found in list %#v", string(attrName), s, valid))
}
return warnings, errors
}
}

View File

@ -15,6 +15,7 @@ import (
azurermprovider "github.com/hashicorp/terraform/builtin/providers/azurerm"
bitbucketprovider "github.com/hashicorp/terraform/builtin/providers/bitbucket"
chefprovider "github.com/hashicorp/terraform/builtin/providers/chef"
circonusprovider "github.com/hashicorp/terraform/builtin/providers/circonus"
clcprovider "github.com/hashicorp/terraform/builtin/providers/clc"
cloudflareprovider "github.com/hashicorp/terraform/builtin/providers/cloudflare"
cloudstackprovider "github.com/hashicorp/terraform/builtin/providers/cloudstack"
@ -89,6 +90,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
"azurerm": azurermprovider.Provider,
"bitbucket": bitbucketprovider.Provider,
"chef": chefprovider.Provider,
"circonus": circonusprovider.Provider,
"clc": clcprovider.Provider,
"cloudflare": cloudflareprovider.Provider,
"cloudstack": cloudstackprovider.Provider,

View File

@ -0,0 +1,28 @@
Copyright (c) 2016, Circonus, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name Circonus, Inc. nor the names
of its contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,163 @@
## Circonus API package
Full api documentation (for using *this* package) is available at [godoc.org](https://godoc.org/github.com/circonus-labs/circonus-gometrics/api). Links in the lists below go directly to the generic Circonus API documentation for the endpoint.
### Straight [raw] API access
* Get
* Post (for creates)
* Put (for updates)
* Delete
### Helpers for currently supported API endpoints
> Note, these interfaces are still being actively developed. For example, many of the `New*` methods only return an empty struct; sensible defaults will be added going forward. Other, common helper methods for the various endpoints may be added as use cases emerge. The organization
of the API may change if common use contexts would benefit significantly.
* [Account](https://login.circonus.com/resources/api/calls/account)
* FetchAccount
* FetchAccounts
* UpdateAccount
* SearchAccounts
* [Acknowledgement](https://login.circonus.com/resources/api/calls/acknowledgement)
* NewAcknowledgement
* FetchAcknowledgement
* FetchAcknowledgements
* UpdateAcknowledgement
* CreateAcknowledgement
* DeleteAcknowledgement
* DeleteAcknowledgementByCID
* SearchAcknowledgements
* [Alert](https://login.circonus.com/resources/api/calls/alert)
* FetchAlert
* FetchAlerts
* SearchAlerts
* [Annotation](https://login.circonus.com/resources/api/calls/annotation)
* NewAnnotation
* FetchAnnotation
* FetchAnnotations
* UpdateAnnotation
* CreateAnnotation
* DeleteAnnotation
* DeleteAnnotationByCID
* SearchAnnotations
* [Broker](https://login.circonus.com/resources/api/calls/broker)
* FetchBroker
* FetchBrokers
* SearchBrokers
* [Check Bundle](https://login.circonus.com/resources/api/calls/check_bundle)
* NewCheckBundle
* FetchCheckBundle
* FetchCheckBundles
* UpdateCheckBundle
* CreateCheckBundle
* DeleteCheckBundle
* DeleteCheckBundleByCID
* SearchCheckBundles
* [Check Bundle Metrics](https://login.circonus.com/resources/api/calls/check_bundle_metrics)
* FetchCheckBundleMetrics
* UpdateCheckBundleMetrics
* [Check](https://login.circonus.com/resources/api/calls/check)
* FetchCheck
* FetchChecks
* SearchChecks
* [Contact Group](https://login.circonus.com/resources/api/calls/contact_group)
* NewContactGroup
* FetchContactGroup
* FetchContactGroups
* UpdateContactGroup
* CreateContactGroup
* DeleteContactGroup
* DeleteContactGroupByCID
* SearchContactGroups
* [Dashboard](https://login.circonus.com/resources/api/calls/dashboard) -- note, this is a work in progress, the methods/types may still change
* NewDashboard
* FetchDashboard
* FetchDashboards
* UpdateDashboard
* CreateDashboard
* DeleteDashboard
* DeleteDashboardByCID
* SearchDashboards
* [Graph](https://login.circonus.com/resources/api/calls/graph)
* NewGraph
* FetchGraph
* FetchGraphs
* UpdateGraph
* CreateGraph
* DeleteGraph
* DeleteGraphByCID
* SearchGraphs
* [Metric Cluster](https://login.circonus.com/resources/api/calls/metric_cluster)
* NewMetricCluster
* FetchMetricCluster
* FetchMetricClusters
* UpdateMetricCluster
* CreateMetricCluster
* DeleteMetricCluster
* DeleteMetricClusterByCID
* SearchMetricClusters
* [Metric](https://login.circonus.com/resources/api/calls/metric)
* FetchMetric
* FetchMetrics
* UpdateMetric
* SearchMetrics
* [Maintenance window](https://login.circonus.com/resources/api/calls/maintenance)
* NewMaintenanceWindow
* FetchMaintenanceWindow
* FetchMaintenanceWindows
* UpdateMaintenanceWindow
* CreateMaintenanceWindow
* DeleteMaintenanceWindow
* DeleteMaintenanceWindowByCID
* SearchMaintenanceWindows
* [Outlier Report](https://login.circonus.com/resources/api/calls/outlier_report)
* NewOutlierReport
* FetchOutlierReport
* FetchOutlierReports
* UpdateOutlierReport
* CreateOutlierReport
* DeleteOutlierReport
* DeleteOutlierReportByCID
* SearchOutlierReports
* [Provision Broker](https://login.circonus.com/resources/api/calls/provision_broker)
* NewProvisionBroker
* FetchProvisionBroker
* UpdateProvisionBroker
* CreateProvisionBroker
* [Rule Set](https://login.circonus.com/resources/api/calls/rule_set)
* NewRuleset
* FetchRuleset
* FetchRulesets
* UpdateRuleset
* CreateRuleset
* DeleteRuleset
* DeleteRulesetByCID
* SearchRulesets
* [Rule Set Group](https://login.circonus.com/resources/api/calls/rule_set_group)
* NewRulesetGroup
* FetchRulesetGroup
* FetchRulesetGroups
* UpdateRulesetGroup
* CreateRulesetGroup
* DeleteRulesetGroup
* DeleteRulesetGroupByCID
* SearchRulesetGroups
* [User](https://login.circonus.com/resources/api/calls/user)
* FetchUser
* FetchUsers
* UpdateUser
* SearchUsers
* [Worksheet](https://login.circonus.com/resources/api/calls/worksheet)
* NewWorksheet
* FetchWorksheet
* FetchWorksheets
* UpdateWorksheet
* CreateWorksheet
* DeleteWorksheet
* DeleteWorksheetByCID
* SearchWorksheets
---
Unless otherwise noted, the source files are distributed under the BSD-style license found in the LICENSE file.

View File

@ -0,0 +1,181 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Account API support - Fetch and Update
// See: https://login.circonus.com/resources/api/calls/account
// Note: Create and Delete are not supported for Accounts via the API
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// AccountLimit defines a usage limit imposed on account
type AccountLimit struct {
Limit uint `json:"_limit,omitempty"` // uint >=0
Type string `json:"_type,omitempty"` // string
Used uint `json:"_used,omitempty"` // uint >=0
}
// AccountInvite defines outstanding invites
type AccountInvite struct {
Email string `json:"email"` // string
Role string `json:"role"` // string
}
// AccountUser defines current users
type AccountUser struct {
Role string `json:"role"` // string
UserCID string `json:"user"` // string
}
// Account defines an account. See https://login.circonus.com/resources/api/calls/account for more information.
type Account struct {
Address1 *string `json:"address1,omitempty"` // string or null
Address2 *string `json:"address2,omitempty"` // string or null
CCEmail *string `json:"cc_email,omitempty"` // string or null
CID string `json:"_cid,omitempty"` // string
City *string `json:"city,omitempty"` // string or null
ContactGroups []string `json:"_contact_groups,omitempty"` // [] len >= 0
Country string `json:"country_code,omitempty"` // string
Description *string `json:"description,omitempty"` // string or null
Invites []AccountInvite `json:"invites,omitempty"` // [] len >= 0
Name string `json:"name,omitempty"` // string
OwnerCID string `json:"_owner,omitempty"` // string
StateProv *string `json:"state_prov,omitempty"` // string or null
Timezone string `json:"timezone,omitempty"` // string
UIBaseURL string `json:"_ui_base_url,omitempty"` // string
Usage []AccountLimit `json:"_usage,omitempty"` // [] len >= 0
Users []AccountUser `json:"users,omitempty"` // [] len >= 0
}
// FetchAccount retrieves account with passed cid. Pass nil for '/account/current'.
func (a *API) FetchAccount(cid CIDType) (*Account, error) {
var accountCID string
if cid == nil || *cid == "" {
accountCID = config.AccountPrefix + "/current"
} else {
accountCID = string(*cid)
}
matched, err := regexp.MatchString(config.AccountCIDRegex, accountCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid account CID [%s]", accountCID)
}
result, err := a.Get(accountCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] account fetch, received JSON: %s", string(result))
}
account := new(Account)
if err := json.Unmarshal(result, account); err != nil {
return nil, err
}
return account, nil
}
// FetchAccounts retrieves all accounts available to the API Token.
func (a *API) FetchAccounts() (*[]Account, error) {
result, err := a.Get(config.AccountPrefix)
if err != nil {
return nil, err
}
var accounts []Account
if err := json.Unmarshal(result, &accounts); err != nil {
return nil, err
}
return &accounts, nil
}
// UpdateAccount updates passed account.
func (a *API) UpdateAccount(cfg *Account) (*Account, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid account config [nil]")
}
accountCID := string(cfg.CID)
matched, err := regexp.MatchString(config.AccountCIDRegex, accountCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid account CID [%s]", accountCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] account update, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(accountCID, jsonCfg)
if err != nil {
return nil, err
}
account := &Account{}
if err := json.Unmarshal(result, account); err != nil {
return nil, err
}
return account, nil
}
// SearchAccounts returns accounts matching a filter (search queries are not
// suppoted by the account endpoint). Pass nil as filter for all accounts the
// API Token can access.
func (a *API) SearchAccounts(filterCriteria *SearchFilterType) (*[]Account, error) {
q := url.Values{}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchAccounts()
}
reqURL := url.URL{
Path: config.AccountPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var accounts []Account
if err := json.Unmarshal(result, &accounts); err != nil {
return nil, err
}
return &accounts, nil
}

View File

@ -0,0 +1,190 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Acknowledgement API support - Fetch, Create, Update, Delete*, and Search
// See: https://login.circonus.com/resources/api/calls/acknowledgement
// * : delete (cancel) by updating with AcknowledgedUntil set to 0
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// Acknowledgement defines a acknowledgement. See https://login.circonus.com/resources/api/calls/acknowledgement for more information.
type Acknowledgement struct {
AcknowledgedBy string `json:"_acknowledged_by,omitempty"` // string
AcknowledgedOn uint `json:"_acknowledged_on,omitempty"` // uint
AcknowledgedUntil interface{} `json:"acknowledged_until,omitempty"` // NOTE received as uint; can be set using string or uint
Active bool `json:"_active,omitempty"` // bool
AlertCID string `json:"alert,omitempty"` // string
CID string `json:"_cid,omitempty"` // string
LastModified uint `json:"_last_modified,omitempty"` // uint
LastModifiedBy string `json:"_last_modified_by,omitempty"` // string
Notes string `json:"notes,omitempty"` // string
}
// NewAcknowledgement returns new Acknowledgement (with defaults, if applicable).
func NewAcknowledgement() *Acknowledgement {
return &Acknowledgement{}
}
// FetchAcknowledgement retrieves acknowledgement with passed cid.
func (a *API) FetchAcknowledgement(cid CIDType) (*Acknowledgement, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid acknowledgement CID [none]")
}
acknowledgementCID := string(*cid)
matched, err := regexp.MatchString(config.AcknowledgementCIDRegex, acknowledgementCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid acknowledgement CID [%s]", acknowledgementCID)
}
result, err := a.Get(acknowledgementCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] acknowledgement fetch, received JSON: %s", string(result))
}
acknowledgement := &Acknowledgement{}
if err := json.Unmarshal(result, acknowledgement); err != nil {
return nil, err
}
return acknowledgement, nil
}
// FetchAcknowledgements retrieves all acknowledgements available to the API Token.
func (a *API) FetchAcknowledgements() (*[]Acknowledgement, error) {
result, err := a.Get(config.AcknowledgementPrefix)
if err != nil {
return nil, err
}
var acknowledgements []Acknowledgement
if err := json.Unmarshal(result, &acknowledgements); err != nil {
return nil, err
}
return &acknowledgements, nil
}
// UpdateAcknowledgement updates passed acknowledgement.
func (a *API) UpdateAcknowledgement(cfg *Acknowledgement) (*Acknowledgement, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid acknowledgement config [nil]")
}
acknowledgementCID := string(cfg.CID)
matched, err := regexp.MatchString(config.AcknowledgementCIDRegex, acknowledgementCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid acknowledgement CID [%s]", acknowledgementCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] acknowledgement update, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(acknowledgementCID, jsonCfg)
if err != nil {
return nil, err
}
acknowledgement := &Acknowledgement{}
if err := json.Unmarshal(result, acknowledgement); err != nil {
return nil, err
}
return acknowledgement, nil
}
// CreateAcknowledgement creates a new acknowledgement.
func (a *API) CreateAcknowledgement(cfg *Acknowledgement) (*Acknowledgement, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid acknowledgement config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
result, err := a.Post(config.AcknowledgementPrefix, jsonCfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] acknowledgement create, sending JSON: %s", string(jsonCfg))
}
acknowledgement := &Acknowledgement{}
if err := json.Unmarshal(result, acknowledgement); err != nil {
return nil, err
}
return acknowledgement, nil
}
// SearchAcknowledgements returns acknowledgements matching
// the specified search query and/or filter. If nil is passed for
// both parameters all acknowledgements will be returned.
func (a *API) SearchAcknowledgements(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Acknowledgement, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchAcknowledgements()
}
reqURL := url.URL{
Path: config.AcknowledgementPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var acknowledgements []Acknowledgement
if err := json.Unmarshal(result, &acknowledgements); err != nil {
return nil, err
}
return &acknowledgements, nil
}

View File

@ -0,0 +1,131 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Alert API support - Fetch and Search
// See: https://login.circonus.com/resources/api/calls/alert
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// Alert defines a alert. See https://login.circonus.com/resources/api/calls/alert for more information.
type Alert struct {
AcknowledgementCID *string `json:"_acknowledgement,omitempty"` // string or null
AlertURL string `json:"_alert_url,omitempty"` // string
BrokerCID string `json:"_broker,omitempty"` // string
CheckCID string `json:"_check,omitempty"` // string
CheckName string `json:"_check_name,omitempty"` // string
CID string `json:"_cid,omitempty"` // string
ClearedOn *uint `json:"_cleared_on,omitempty"` // uint or null
ClearedValue *string `json:"_cleared_value,omitempty"` // string or null
Maintenance []string `json:"_maintenance,omitempty"` // [] len >= 0
MetricLinkURL *string `json:"_metric_link,omitempty"` // string or null
MetricName string `json:"_metric_name,omitempty"` // string
MetricNotes *string `json:"_metric_notes,omitempty"` // string or null
OccurredOn uint `json:"_occurred_on,omitempty"` // uint
RuleSetCID string `json:"_rule_set,omitempty"` // string
Severity uint `json:"_severity,omitempty"` // uint
Tags []string `json:"_tags,omitempty"` // [] len >= 0
Value string `json:"_value,omitempty"` // string
}
// NewAlert returns a new alert (with defaults, if applicable)
func NewAlert() *Alert {
return &Alert{}
}
// FetchAlert retrieves alert with passed cid.
func (a *API) FetchAlert(cid CIDType) (*Alert, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid alert CID [none]")
}
alertCID := string(*cid)
matched, err := regexp.MatchString(config.AlertCIDRegex, alertCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid alert CID [%s]", alertCID)
}
result, err := a.Get(alertCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch alert, received JSON: %s", string(result))
}
alert := &Alert{}
if err := json.Unmarshal(result, alert); err != nil {
return nil, err
}
return alert, nil
}
// FetchAlerts retrieves all alerts available to the API Token.
func (a *API) FetchAlerts() (*[]Alert, error) {
result, err := a.Get(config.AlertPrefix)
if err != nil {
return nil, err
}
var alerts []Alert
if err := json.Unmarshal(result, &alerts); err != nil {
return nil, err
}
return &alerts, nil
}
// SearchAlerts returns alerts matching the specified search query
// and/or filter. If nil is passed for both parameters all alerts
// will be returned.
func (a *API) SearchAlerts(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Alert, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchAlerts()
}
reqURL := url.URL{
Path: config.AlertPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var alerts []Alert
if err := json.Unmarshal(result, &alerts); err != nil {
return nil, err
}
return &alerts, nil
}

View File

@ -0,0 +1,223 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Annotation API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/annotation
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// Annotation defines a annotation. See https://login.circonus.com/resources/api/calls/annotation for more information.
type Annotation struct {
Category string `json:"category"` // string
CID string `json:"_cid,omitempty"` // string
Created uint `json:"_created,omitempty"` // uint
Description string `json:"description"` // string
LastModified uint `json:"_last_modified,omitempty"` // uint
LastModifiedBy string `json:"_last_modified_by,omitempty"` // string
RelatedMetrics []string `json:"rel_metrics"` // [] len >= 0
Start uint `json:"start"` // uint
Stop uint `json:"stop"` // uint
Title string `json:"title"` // string
}
// NewAnnotation returns a new Annotation (with defaults, if applicable)
func NewAnnotation() *Annotation {
return &Annotation{}
}
// FetchAnnotation retrieves annotation with passed cid.
func (a *API) FetchAnnotation(cid CIDType) (*Annotation, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid annotation CID [none]")
}
annotationCID := string(*cid)
matched, err := regexp.MatchString(config.AnnotationCIDRegex, annotationCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid annotation CID [%s]", annotationCID)
}
result, err := a.Get(annotationCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch annotation, received JSON: %s", string(result))
}
annotation := &Annotation{}
if err := json.Unmarshal(result, annotation); err != nil {
return nil, err
}
return annotation, nil
}
// FetchAnnotations retrieves all annotations available to the API Token.
func (a *API) FetchAnnotations() (*[]Annotation, error) {
result, err := a.Get(config.AnnotationPrefix)
if err != nil {
return nil, err
}
var annotations []Annotation
if err := json.Unmarshal(result, &annotations); err != nil {
return nil, err
}
return &annotations, nil
}
// UpdateAnnotation updates passed annotation.
func (a *API) UpdateAnnotation(cfg *Annotation) (*Annotation, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid annotation config [nil]")
}
annotationCID := string(cfg.CID)
matched, err := regexp.MatchString(config.AnnotationCIDRegex, annotationCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid annotation CID [%s]", annotationCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update annotation, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(annotationCID, jsonCfg)
if err != nil {
return nil, err
}
annotation := &Annotation{}
if err := json.Unmarshal(result, annotation); err != nil {
return nil, err
}
return annotation, nil
}
// CreateAnnotation creates a new annotation.
func (a *API) CreateAnnotation(cfg *Annotation) (*Annotation, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid annotation config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] create annotation, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.AnnotationPrefix, jsonCfg)
if err != nil {
return nil, err
}
annotation := &Annotation{}
if err := json.Unmarshal(result, annotation); err != nil {
return nil, err
}
return annotation, nil
}
// DeleteAnnotation deletes passed annotation.
func (a *API) DeleteAnnotation(cfg *Annotation) (bool, error) {
if cfg == nil {
return false, fmt.Errorf("Invalid annotation config [nil]")
}
return a.DeleteAnnotationByCID(CIDType(&cfg.CID))
}
// DeleteAnnotationByCID deletes annotation with passed cid.
func (a *API) DeleteAnnotationByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, fmt.Errorf("Invalid annotation CID [none]")
}
annotationCID := string(*cid)
matched, err := regexp.MatchString(config.AnnotationCIDRegex, annotationCID)
if err != nil {
return false, err
}
if !matched {
return false, fmt.Errorf("Invalid annotation CID [%s]", annotationCID)
}
_, err = a.Delete(annotationCID)
if err != nil {
return false, err
}
return true, nil
}
// SearchAnnotations returns annotations matching the specified
// search query and/or filter. If nil is passed for both parameters
// all annotations will be returned.
func (a *API) SearchAnnotations(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Annotation, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchAnnotations()
}
reqURL := url.URL{
Path: config.AnnotationPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var annotations []Annotation
if err := json.Unmarshal(result, &annotations); err != nil {
return nil, err
}
return &annotations, nil
}

View File

@ -0,0 +1,371 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package api
import (
"bytes"
crand "crypto/rand"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"log"
"math"
"math/big"
"math/rand"
"net"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"sync"
"time"
"github.com/hashicorp/go-retryablehttp"
)
func init() {
n, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
rand.Seed(time.Now().UTC().UnixNano())
return
}
rand.Seed(n.Int64())
}
const (
// a few sensible defaults
defaultAPIURL = "https://api.circonus.com/v2"
defaultAPIApp = "circonus-gometrics"
minRetryWait = 1 * time.Second
maxRetryWait = 15 * time.Second
maxRetries = 4 // equating to 1 + maxRetries total attempts
)
// TokenKeyType - Circonus API Token key
type TokenKeyType string
// TokenAppType - Circonus API Token app name
type TokenAppType string
// CIDType Circonus object cid
type CIDType *string
// IDType Circonus object id
type IDType int
// URLType submission url type
type URLType string
// SearchQueryType search query (see: https://login.circonus.com/resources/api#searching)
type SearchQueryType string
// SearchFilterType search filter (see: https://login.circonus.com/resources/api#filtering)
type SearchFilterType map[string][]string
// TagType search/select/custom tag(s) type
type TagType []string
// Config options for Circonus API
type Config struct {
URL string
TokenKey string
TokenApp string
CACert *x509.CertPool
Log *log.Logger
Debug bool
}
// API Circonus API
type API struct {
apiURL *url.URL
key TokenKeyType
app TokenAppType
caCert *x509.CertPool
Debug bool
Log *log.Logger
useExponentialBackoff bool
useExponentialBackoffmu sync.Mutex
}
// NewClient returns a new Circonus API (alias for New)
func NewClient(ac *Config) (*API, error) {
return New(ac)
}
// NewAPI returns a new Circonus API (alias for New)
func NewAPI(ac *Config) (*API, error) {
return New(ac)
}
// New returns a new Circonus API
func New(ac *Config) (*API, error) {
if ac == nil {
return nil, errors.New("Invalid API configuration (nil)")
}
key := TokenKeyType(ac.TokenKey)
if key == "" {
return nil, errors.New("API Token is required")
}
app := TokenAppType(ac.TokenApp)
if app == "" {
app = defaultAPIApp
}
au := string(ac.URL)
if au == "" {
au = defaultAPIURL
}
if !strings.Contains(au, "/") {
// if just a hostname is passed, ASSume "https" and a path prefix of "/v2"
au = fmt.Sprintf("https://%s/v2", ac.URL)
}
if last := len(au) - 1; last >= 0 && au[last] == '/' {
// strip off trailing '/'
au = au[:last]
}
apiURL, err := url.Parse(au)
if err != nil {
return nil, err
}
a := &API{
apiURL: apiURL,
key: key,
app: app,
caCert: ac.CACert,
Debug: ac.Debug,
Log: ac.Log,
useExponentialBackoff: false,
}
a.Debug = ac.Debug
a.Log = ac.Log
if a.Debug && a.Log == nil {
a.Log = log.New(os.Stderr, "", log.LstdFlags)
}
if a.Log == nil {
a.Log = log.New(ioutil.Discard, "", log.LstdFlags)
}
return a, nil
}
// EnableExponentialBackoff enables use of exponential backoff for next API call(s)
// and use exponential backoff for all API calls until exponential backoff is disabled.
func (a *API) EnableExponentialBackoff() {
a.useExponentialBackoffmu.Lock()
a.useExponentialBackoff = true
a.useExponentialBackoffmu.Unlock()
}
// DisableExponentialBackoff disables use of exponential backoff. If a request using
// exponential backoff is currently running, it will stop using exponential backoff
// on its next iteration (if needed).
func (a *API) DisableExponentialBackoff() {
a.useExponentialBackoffmu.Lock()
a.useExponentialBackoff = false
a.useExponentialBackoffmu.Unlock()
}
// Get API request
func (a *API) Get(reqPath string) ([]byte, error) {
return a.apiRequest("GET", reqPath, nil)
}
// Delete API request
func (a *API) Delete(reqPath string) ([]byte, error) {
return a.apiRequest("DELETE", reqPath, nil)
}
// Post API request
func (a *API) Post(reqPath string, data []byte) ([]byte, error) {
return a.apiRequest("POST", reqPath, data)
}
// Put API request
func (a *API) Put(reqPath string, data []byte) ([]byte, error) {
return a.apiRequest("PUT", reqPath, data)
}
func backoff(interval uint) float64 {
return math.Floor(((float64(interval) * (1 + rand.Float64())) / 2) + .5)
}
// apiRequest manages retry strategy for exponential backoffs
func (a *API) apiRequest(reqMethod string, reqPath string, data []byte) ([]byte, error) {
backoffs := []uint{2, 4, 8, 16, 32}
attempts := 0
success := false
var result []byte
var err error
for !success {
result, err = a.apiCall(reqMethod, reqPath, data)
if err == nil {
success = true
}
// break and return error if not using exponential backoff
if err != nil {
if !a.useExponentialBackoff {
break
}
if matched, _ := regexp.MatchString("code 403", err.Error()); matched {
break
}
}
if !success {
var wait float64
if attempts >= len(backoffs) {
wait = backoff(backoffs[len(backoffs)-1])
} else {
wait = backoff(backoffs[attempts])
}
attempts++
a.Log.Printf("[WARN] API call failed %s, retrying in %d seconds.\n", err.Error(), uint(wait))
time.Sleep(time.Duration(wait) * time.Second)
}
}
return result, err
}
// apiCall call Circonus API
func (a *API) apiCall(reqMethod string, reqPath string, data []byte) ([]byte, error) {
reqURL := a.apiURL.String()
if reqPath == "" {
return nil, errors.New("Invalid URL path")
}
if reqPath[:1] != "/" {
reqURL += "/"
}
if len(reqPath) >= 3 && reqPath[:3] == "/v2" {
reqURL += reqPath[3:]
} else {
reqURL += reqPath
}
// keep last HTTP error in the event of retry failure
var lastHTTPError error
retryPolicy := func(resp *http.Response, err error) (bool, error) {
if err != nil {
lastHTTPError = err
return true, err
}
// Check the response code. We retry on 500-range responses to allow
// the server time to recover, as 500's are typically not permanent
// errors and may relate to outages on the server side. This will catch
// invalid response codes as well, like 0 and 999.
// Retry on 429 (rate limit) as well.
if resp.StatusCode == 0 || // wtf?!
resp.StatusCode >= 500 || // rutroh
resp.StatusCode == 429 { // rate limit
body, readErr := ioutil.ReadAll(resp.Body)
if readErr != nil {
lastHTTPError = fmt.Errorf("- response: %d %s", resp.StatusCode, readErr.Error())
} else {
lastHTTPError = fmt.Errorf("- response: %d %s", resp.StatusCode, strings.TrimSpace(string(body)))
}
return true, nil
}
return false, nil
}
dataReader := bytes.NewReader(data)
req, err := retryablehttp.NewRequest(reqMethod, reqURL, dataReader)
if err != nil {
return nil, fmt.Errorf("[ERROR] creating API request: %s %+v", reqURL, err)
}
req.Header.Add("Accept", "application/json")
req.Header.Add("X-Circonus-Auth-Token", string(a.key))
req.Header.Add("X-Circonus-App-Name", string(a.app))
client := retryablehttp.NewClient()
if a.apiURL.Scheme == "https" && a.caCert != nil {
client.HTTPClient.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{RootCAs: a.caCert},
DisableKeepAlives: true,
MaxIdleConnsPerHost: -1,
DisableCompression: true,
}
} else {
client.HTTPClient.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
DisableKeepAlives: true,
MaxIdleConnsPerHost: -1,
DisableCompression: true,
}
}
a.useExponentialBackoffmu.Lock()
eb := a.useExponentialBackoff
a.useExponentialBackoffmu.Unlock()
if eb {
// limit to one request if using exponential backoff
client.RetryWaitMin = 1
client.RetryWaitMax = 2
client.RetryMax = 0
} else {
client.RetryWaitMin = minRetryWait
client.RetryWaitMax = maxRetryWait
client.RetryMax = maxRetries
}
// retryablehttp only groks log or no log
if a.Debug {
client.Logger = a.Log
} else {
client.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
}
client.CheckRetry = retryPolicy
resp, err := client.Do(req)
if err != nil {
if lastHTTPError != nil {
return nil, lastHTTPError
}
return nil, fmt.Errorf("[ERROR] %s: %+v", reqURL, err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("[ERROR] reading response %+v", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
msg := fmt.Sprintf("API response code %d: %s", resp.StatusCode, string(body))
if a.Debug {
a.Log.Printf("[DEBUG] %s\n", msg)
}
return nil, fmt.Errorf("[ERROR] %s", msg)
}
return body, nil
}

View File

@ -0,0 +1,131 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Broker API support - Fetch and Search
// See: https://login.circonus.com/resources/api/calls/broker
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// BrokerDetail defines instance attributes
type BrokerDetail struct {
CN string `json:"cn"` // string
ExternalHost *string `json:"external_host"` // string or null
ExternalPort uint16 `json:"external_port"` // uint16
IP *string `json:"ipaddress"` // string or null
MinVer uint `json:"minimum_version_required"` // uint
Modules []string `json:"modules"` // [] len >= 0
Port *uint16 `json:"port"` // uint16 or null
Skew *string `json:"skew"` // BUG doc: floating point number, api object: string or null
Status string `json:"status"` // string
Version *uint `json:"version"` // uint or null
}
// Broker defines a broker. See https://login.circonus.com/resources/api/calls/broker for more information.
type Broker struct {
CID string `json:"_cid"` // string
Details []BrokerDetail `json:"_details"` // [] len >= 1
Latitude *string `json:"_latitude"` // string or null
Longitude *string `json:"_longitude"` // string or null
Name string `json:"_name"` // string
Tags []string `json:"_tags"` // [] len >= 0
Type string `json:"_type"` // string
}
// FetchBroker retrieves broker with passed cid.
func (a *API) FetchBroker(cid CIDType) (*Broker, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid broker CID [none]")
}
brokerCID := string(*cid)
matched, err := regexp.MatchString(config.BrokerCIDRegex, brokerCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid broker CID [%s]", brokerCID)
}
result, err := a.Get(brokerCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch broker, received JSON: %s", string(result))
}
response := new(Broker)
if err := json.Unmarshal(result, &response); err != nil {
return nil, err
}
return response, nil
}
// FetchBrokers returns all brokers available to the API Token.
func (a *API) FetchBrokers() (*[]Broker, error) {
result, err := a.Get(config.BrokerPrefix)
if err != nil {
return nil, err
}
var response []Broker
if err := json.Unmarshal(result, &response); err != nil {
return nil, err
}
return &response, nil
}
// SearchBrokers returns brokers matching the specified search
// query and/or filter. If nil is passed for both parameters
// all brokers will be returned.
func (a *API) SearchBrokers(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Broker, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchBrokers()
}
reqURL := url.URL{
Path: config.BrokerPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var brokers []Broker
if err := json.Unmarshal(result, &brokers); err != nil {
return nil, err
}
return &brokers, nil
}

View File

@ -0,0 +1,119 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Check API support - Fetch and Search
// See: https://login.circonus.com/resources/api/calls/check
// Notes: checks do not directly support create, update, and delete - see check bundle.
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// CheckDetails contains [undocumented] check type specific information
type CheckDetails map[config.Key]string
// Check defines a check. See https://login.circonus.com/resources/api/calls/check for more information.
type Check struct {
Active bool `json:"_active"` // bool
BrokerCID string `json:"_broker"` // string
CheckBundleCID string `json:"_check_bundle"` // string
CheckUUID string `json:"_check_uuid"` // string
CID string `json:"_cid"` // string
Details CheckDetails `json:"_details"` // NOTE contents of details are check type specific, map len >= 0
}
// FetchCheck retrieves check with passed cid.
func (a *API) FetchCheck(cid CIDType) (*Check, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid check CID [none]")
}
checkCID := string(*cid)
matched, err := regexp.MatchString(config.CheckCIDRegex, checkCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid check CID [%s]", checkCID)
}
result, err := a.Get(checkCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch check, received JSON: %s", string(result))
}
check := new(Check)
if err := json.Unmarshal(result, check); err != nil {
return nil, err
}
return check, nil
}
// FetchChecks retrieves all checks available to the API Token.
func (a *API) FetchChecks() (*[]Check, error) {
result, err := a.Get(config.CheckPrefix)
if err != nil {
return nil, err
}
var checks []Check
if err := json.Unmarshal(result, &checks); err != nil {
return nil, err
}
return &checks, nil
}
// SearchChecks returns checks matching the specified search query
// and/or filter. If nil is passed for both parameters all checks
// will be returned.
func (a *API) SearchChecks(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Check, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchChecks()
}
reqURL := url.URL{
Path: config.CheckPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, err
}
var checks []Check
if err := json.Unmarshal(result, &checks); err != nil {
return nil, err
}
return &checks, nil
}

View File

@ -0,0 +1,255 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Check bundle API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/check_bundle
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// CheckBundleMetric individual metric configuration
type CheckBundleMetric struct {
Name string `json:"name"` // string
Result *string `json:"result,omitempty"` // string or null, NOTE not settable - return/information value only
Status string `json:"status,omitempty"` // string
Tags []string `json:"tags"` // [] len >= 0
Type string `json:"type"` // string
Units *string `json:"units,omitempty"` // string or null
}
// CheckBundleConfig contains the check type specific configuration settings
// as k/v pairs (see https://login.circonus.com/resources/api/calls/check_bundle
// for the specific settings available for each distinct check type)
type CheckBundleConfig map[config.Key]string
// CheckBundle defines a check bundle. See https://login.circonus.com/resources/api/calls/check_bundle for more information.
type CheckBundle struct {
Brokers []string `json:"brokers"` // [] len >= 0
Checks []string `json:"_checks,omitempty"` // [] len >= 0
CheckUUIDs []string `json:"_check_uuids,omitempty"` // [] len >= 0
CID string `json:"_cid,omitempty"` // string
Config CheckBundleConfig `json:"config,omitempty"` // NOTE contents of config are check type specific, map len >= 0
Created uint `json:"_created,omitempty"` // uint
DisplayName string `json:"display_name"` // string
LastModifedBy string `json:"_last_modifed_by,omitempty"` // string
LastModified uint `json:"_last_modified,omitempty"` // uint
MetricLimit int `json:"metric_limit,omitempty"` // int
Metrics []CheckBundleMetric `json:"metrics"` // [] >= 0
Notes *string `json:"notes,omitempty"` // string or null
Period uint `json:"period,omitempty"` // uint
ReverseConnectURLs []string `json:"_reverse_connection_urls,omitempty"` // [] len >= 0
Status string `json:"status,omitempty"` // string
Tags []string `json:"tags,omitempty"` // [] len >= 0
Target string `json:"target"` // string
Timeout float32 `json:"timeout,omitempty"` // float32
Type string `json:"type"` // string
}
// NewCheckBundle returns new CheckBundle (with defaults, if applicable)
func NewCheckBundle() *CheckBundle {
return &CheckBundle{
Config: make(CheckBundleConfig, config.DefaultConfigOptionsSize),
MetricLimit: config.DefaultCheckBundleMetricLimit,
Period: config.DefaultCheckBundlePeriod,
Timeout: config.DefaultCheckBundleTimeout,
Status: config.DefaultCheckBundleStatus,
}
}
// FetchCheckBundle retrieves check bundle with passed cid.
func (a *API) FetchCheckBundle(cid CIDType) (*CheckBundle, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid check bundle CID [none]")
}
bundleCID := string(*cid)
matched, err := regexp.MatchString(config.CheckBundleCIDRegex, bundleCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid check bundle CID [%v]", bundleCID)
}
result, err := a.Get(bundleCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch check bundle, received JSON: %s", string(result))
}
checkBundle := &CheckBundle{}
if err := json.Unmarshal(result, checkBundle); err != nil {
return nil, err
}
return checkBundle, nil
}
// FetchCheckBundles retrieves all check bundles available to the API Token.
func (a *API) FetchCheckBundles() (*[]CheckBundle, error) {
result, err := a.Get(config.CheckBundlePrefix)
if err != nil {
return nil, err
}
var checkBundles []CheckBundle
if err := json.Unmarshal(result, &checkBundles); err != nil {
return nil, err
}
return &checkBundles, nil
}
// UpdateCheckBundle updates passed check bundle.
func (a *API) UpdateCheckBundle(cfg *CheckBundle) (*CheckBundle, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid check bundle config [nil]")
}
bundleCID := string(cfg.CID)
matched, err := regexp.MatchString(config.CheckBundleCIDRegex, bundleCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid check bundle CID [%s]", bundleCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update check bundle, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(bundleCID, jsonCfg)
if err != nil {
return nil, err
}
checkBundle := &CheckBundle{}
if err := json.Unmarshal(result, checkBundle); err != nil {
return nil, err
}
return checkBundle, nil
}
// CreateCheckBundle creates a new check bundle (check).
func (a *API) CreateCheckBundle(cfg *CheckBundle) (*CheckBundle, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid check bundle config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] create check bundle, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.CheckBundlePrefix, jsonCfg)
if err != nil {
return nil, err
}
checkBundle := &CheckBundle{}
if err := json.Unmarshal(result, checkBundle); err != nil {
return nil, err
}
return checkBundle, nil
}
// DeleteCheckBundle deletes passed check bundle.
func (a *API) DeleteCheckBundle(cfg *CheckBundle) (bool, error) {
if cfg == nil {
return false, fmt.Errorf("Invalid check bundle config [nil]")
}
return a.DeleteCheckBundleByCID(CIDType(&cfg.CID))
}
// DeleteCheckBundleByCID deletes check bundle with passed cid.
func (a *API) DeleteCheckBundleByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, fmt.Errorf("Invalid check bundle CID [none]")
}
bundleCID := string(*cid)
matched, err := regexp.MatchString(config.CheckBundleCIDRegex, bundleCID)
if err != nil {
return false, err
}
if !matched {
return false, fmt.Errorf("Invalid check bundle CID [%v]", bundleCID)
}
_, err = a.Delete(bundleCID)
if err != nil {
return false, err
}
return true, nil
}
// SearchCheckBundles returns check bundles matching the specified
// search query and/or filter. If nil is passed for both parameters
// all check bundles will be returned.
func (a *API) SearchCheckBundles(searchCriteria *SearchQueryType, filterCriteria *map[string][]string) (*[]CheckBundle, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchCheckBundles()
}
reqURL := url.URL{
Path: config.CheckBundlePrefix,
RawQuery: q.Encode(),
}
resp, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var results []CheckBundle
if err := json.Unmarshal(resp, &results); err != nil {
return nil, err
}
return &results, nil
}

View File

@ -0,0 +1,95 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// CheckBundleMetrics API support - Fetch, Create*, Update, and Delete**
// See: https://login.circonus.com/resources/api/calls/check_bundle_metrics
// * : create metrics by adding to array with a status of 'active'
// ** : delete (distable collection of) metrics by changing status from 'active' to 'available'
package api
import (
"encoding/json"
"fmt"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// CheckBundleMetrics defines metrics for a specific check bundle. See https://login.circonus.com/resources/api/calls/check_bundle_metrics for more information.
type CheckBundleMetrics struct {
CID string `json:"_cid,omitempty"` // string
Metrics []CheckBundleMetric `json:"metrics"` // See check_bundle.go for CheckBundleMetric definition
}
// FetchCheckBundleMetrics retrieves metrics for the check bundle with passed cid.
func (a *API) FetchCheckBundleMetrics(cid CIDType) (*CheckBundleMetrics, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid check bundle metrics CID [none]")
}
metricsCID := string(*cid)
matched, err := regexp.MatchString(config.CheckBundleMetricsCIDRegex, metricsCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid check bundle metrics CID [%s]", metricsCID)
}
result, err := a.Get(metricsCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch check bundle metrics, received JSON: %s", string(result))
}
metrics := &CheckBundleMetrics{}
if err := json.Unmarshal(result, metrics); err != nil {
return nil, err
}
return metrics, nil
}
// UpdateCheckBundleMetrics updates passed metrics.
func (a *API) UpdateCheckBundleMetrics(cfg *CheckBundleMetrics) (*CheckBundleMetrics, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid check bundle metrics config [nil]")
}
metricsCID := string(cfg.CID)
matched, err := regexp.MatchString(config.CheckBundleMetricsCIDRegex, metricsCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid check bundle metrics CID [%s]", metricsCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update check bundle metrics, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(metricsCID, jsonCfg)
if err != nil {
return nil, err
}
metrics := &CheckBundleMetrics{}
if err := json.Unmarshal(result, metrics); err != nil {
return nil, err
}
return metrics, nil
}

View File

@ -0,0 +1,538 @@
package config
// Key for CheckBundleConfig options and CheckDetails info
type Key string
// Constants per type as defined in
// https://login.circonus.com/resources/api/calls/check_bundle
const (
//
// default settings for api.NewCheckBundle()
//
DefaultCheckBundleMetricLimit = -1 // unlimited
DefaultCheckBundleStatus = "active"
DefaultCheckBundlePeriod = 60
DefaultCheckBundleTimeout = 10
DefaultConfigOptionsSize = 20
//
// common (apply to more than one check type)
//
AsyncMetrics = Key("asynch_metrics")
AuthMethod = Key("auth_method")
AuthPassword = Key("auth_password")
AuthUser = Key("auth_user")
BaseURL = Key("base_url")
CAChain = Key("ca_chain")
CertFile = Key("certificate_file")
Ciphers = Key("ciphers")
Command = Key("command")
DSN = Key("dsn")
HeaderPrefix = Key("header_")
HTTPVersion = Key("http_version")
KeyFile = Key("key_file")
Method = Key("method")
Password = Key("password")
Payload = Key("payload")
Port = Key("port")
Query = Key("query")
ReadLimit = Key("read_limit")
Secret = Key("secret")
SQL = Key("sql")
URI = Key("uri")
URL = Key("url")
Username = Key("username")
UseSSL = Key("use_ssl")
User = Key("user")
SASLAuthentication = Key("sasl_authentication")
SASLUser = Key("sasl_user")
SecurityLevel = Key("security_level")
Version = Key("version")
AppendColumnName = Key("append_column_name")
Database = Key("database")
JDBCPrefix = Key("jdbc_")
//
// CAQL check
//
// Common items:
// Query
//
// Circonus Windows Agent
//
// Common items:
// AuthPassword
// AuthUser
// Port
// URL
Calculated = Key("calculated")
Category = Key("category")
//
// Cloudwatch
//
// Notes:
// DimPrefix is special because the actual key is dynamic and matches: `dim_(.+)`
// Common items:
// URL
// Version
APIKey = Key("api_key")
APISecret = Key("api_secret")
CloudwatchMetrics = Key("cloudwatch_metrics")
DimPrefix = Key("dim_")
Granularity = Key("granularity")
Namespace = Key("namespace")
Statistics = Key("statistics")
//
// Collectd
//
// Common items:
// AsyncMetrics
// Username
// Secret
// SecurityLevel
//
// Composite
//
CompositeMetricName = Key("composite_metric_name")
Formula = Key("formula")
//
// DHCP
//
HardwareAddress = Key("hardware_addr")
HostIP = Key("host_ip")
RequestType = Key("request_type")
SendPort = Key("send_port")
//
// DNS
//
// Common items:
// Query
CType = Key("ctype")
Nameserver = Key("nameserver")
RType = Key("rtype")
//
// EC Console
//
// Common items:
// Command
// Port
// SASLAuthentication
// SASLUser
Objects = Key("objects")
XPath = Key("xpath")
//
// Elastic Search
//
// Common items:
// Port
// URL
//
// Ganglia
//
// Common items:
// AsyncMetrics
//
// Google Analytics
//
// Common items:
// Password
// Username
OAuthToken = Key("oauth_token")
OAuthTokenSecret = Key("oauth_token_secret")
OAuthVersion = Key("oauth_version")
TableID = Key("table_id")
UseOAuth = Key("use_oauth")
//
// HA Proxy
//
// Common items:
// AuthPassword
// AuthUser
// Port
// UseSSL
Host = Key("host")
Select = Key("select")
//
// HTTP
//
// Notes:
// HeaderPrefix is special because the actual key is dynamic and matches: `header_(\S+)`
// Common items:
// AuthMethod
// AuthPassword
// AuthUser
// CAChain
// CertFile
// Ciphers
// KeyFile
// URL
// HeaderPrefix
// HTTPVersion
// Method
// Payload
// ReadLimit
Body = Key("body")
Code = Key("code")
Extract = Key("extract")
Redirects = Key("redirects")
//
// HTTPTRAP
//
// Common items:
// AsyncMetrics
// Secret
//
// IMAP
//
// Common items:
// AuthPassword
// AuthUser
// CAChain
// CertFile
// Ciphers
// KeyFile
// Port
// UseSSL
Fetch = Key("fetch")
Folder = Key("folder")
HeaderHost = Key("header_Host")
Search = Key("search")
//
// JMX
//
// Common items:
// Password
// Port
// URI
// Username
MbeanDomains = Key("mbean_domains")
//
// JSON
//
// Common items:
// AuthMethod
// AuthPassword
// AuthUser
// CAChain
// CertFile
// Ciphers
// HeaderPrefix
// HTTPVersion
// KeyFile
// Method
// Payload
// Port
// ReadLimit
// URL
//
// Keynote
//
// Notes:
// SlotAliasPrefix is special because the actual key is dynamic and matches: `slot_alias_(\d+)`
// Common items:
// APIKey
// BaseURL
PageComponent = Key("pagecomponent")
SlotAliasPrefix = Key("slot_alias_")
SlotIDList = Key("slot_id_list")
TransPageList = Key("transpagelist")
//
// Keynote Pulse
//
// Common items:
// BaseURL
// Password
// User
AgreementID = Key("agreement_id")
//
// LDAP
//
// Common items:
// Password
// Port
AuthType = Key("authtype")
DN = Key("dn")
SecurityPrincipal = Key("security_principal")
//
// Memcached
//
// Common items:
// Port
//
// MongoDB
//
// Common items:
// Command
// Password
// Port
// Username
DBName = Key("dbname")
//
// Munin
//
// Note: no configuration options
//
// MySQL
//
// Common items:
// DSN
// SQL
//
// Newrelic rpm
//
// Common items:
// APIKey
AccountID = Key("acct_id")
ApplicationID = Key("application_id")
LicenseKey = Key("license_key")
//
// Nginx
//
// Common items:
// CAChain
// CertFile
// Ciphers
// KeyFile
// URL
//
// NRPE
//
// Common items:
// Command
// Port
// UseSSL
AppendUnits = Key("append_uom")
//
// NTP
//
// Common items:
// Port
Control = Key("control")
//
// Oracle
//
// Notes:
// JDBCPrefix is special because the actual key is dynamic and matches: `jdbc_(\S+)`
// Common items:
// AppendColumnName
// Database
// JDBCPrefix
// Password
// Port
// SQL
// User
//
// Ping ICMP
//
AvailNeeded = Key("avail_needed")
Count = Key("count")
Interval = Key("interval")
//
// PostgreSQL
//
// Common items:
// DSN
// SQL
//
// Redis
//
// Common items:
// Command
// Password
// Port
DBIndex = Key("dbindex")
//
// Resmon
//
// Notes:
// HeaderPrefix is special because the actual key is dynamic and matches: `header_(\S+)`
// Common items:
// AuthMethod
// AuthPassword
// AuthUser
// CAChain
// CertFile
// Ciphers
// HeaderPrefix
// HTTPVersion
// KeyFile
// Method
// Payload
// Port
// ReadLimit
// URL
//
// SMTP
//
// Common items:
// Payload
// Port
// SASLAuthentication
// SASLUser
EHLO = Key("ehlo")
From = Key("from")
SASLAuthID = Key("sasl_auth_id")
SASLPassword = Key("sasl_password")
StartTLS = Key("starttls")
To = Key("to")
//
// SNMP
//
// Notes:
// OIDPrefix is special because the actual key is dynamic and matches: `oid_(.+)`
// TypePrefix is special because the actual key is dynamic and matches: `type_(.+)`
// Common items:
// Port
// SecurityLevel
// Version
AuthPassphrase = Key("auth_passphrase")
AuthProtocol = Key("auth_protocol")
Community = Key("community")
ContextEngine = Key("context_engine")
ContextName = Key("context_name")
OIDPrefix = Key("oid_")
PrivacyPassphrase = Key("privacy_passphrase")
PrivacyProtocol = Key("privacy_protocol")
SecurityEngine = Key("security_engine")
SecurityName = Key("security_name")
SeparateQueries = Key("separate_queries")
TypePrefix = Key("type_")
//
// SQLServer
//
// Notes:
// JDBCPrefix is special because the actual key is dynamic and matches: `jdbc_(\S+)`
// Common items:
// AppendColumnName
// Database
// JDBCPrefix
// Password
// Port
// SQL
// User
//
// SSH v2
//
// Common items:
// Port
MethodCompCS = Key("method_comp_cs")
MethodCompSC = Key("method_comp_sc")
MethodCryptCS = Key("method_crypt_cs")
MethodCryptSC = Key("method_crypt_sc")
MethodHostKey = Key("method_hostkey")
MethodKeyExchange = Key("method_kex")
MethodMacCS = Key("method_mac_cs")
MethodMacSC = Key("method_mac_sc")
//
// StatsD
//
// Note: no configuration options
//
// TCP
//
// Common items:
// CAChain
// CertFile
// Ciphers
// KeyFile
// Port
// UseSSL
BannerMatch = Key("banner_match")
//
// Varnish
//
// Note: no configuration options
//
// reserved - config option(s) can't actually be set - here for r/o access
//
ReverseSecretKey = Key("reverse:secret_key")
SubmissionURL = Key("submission_url")
//
// Endpoint prefix & cid regex
//
DefaultCIDRegex = "[0-9]+"
DefaultUUIDRegex = "[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}"
AccountPrefix = "/account"
AccountCIDRegex = "^(" + AccountPrefix + "/(" + DefaultCIDRegex + "|current))$"
AcknowledgementPrefix = "/acknowledgement"
AcknowledgementCIDRegex = "^(" + AcknowledgementPrefix + "/(" + DefaultCIDRegex + "))$"
AlertPrefix = "/alert"
AlertCIDRegex = "^(" + AlertPrefix + "/(" + DefaultCIDRegex + "))$"
AnnotationPrefix = "/annotation"
AnnotationCIDRegex = "^(" + AnnotationPrefix + "/(" + DefaultCIDRegex + "))$"
BrokerPrefix = "/broker"
BrokerCIDRegex = "^(" + BrokerPrefix + "/(" + DefaultCIDRegex + "))$"
CheckBundleMetricsPrefix = "/check_bundle_metrics"
CheckBundleMetricsCIDRegex = "^(" + CheckBundleMetricsPrefix + "/(" + DefaultCIDRegex + "))$"
CheckBundlePrefix = "/check_bundle"
CheckBundleCIDRegex = "^(" + CheckBundlePrefix + "/(" + DefaultCIDRegex + "))$"
CheckPrefix = "/check"
CheckCIDRegex = "^(" + CheckPrefix + "/(" + DefaultCIDRegex + "))$"
ContactGroupPrefix = "/contact_group"
ContactGroupCIDRegex = "^(" + ContactGroupPrefix + "/(" + DefaultCIDRegex + "))$"
DashboardPrefix = "/dashboard"
DashboardCIDRegex = "^(" + DashboardPrefix + "/(" + DefaultCIDRegex + "))$"
GraphPrefix = "/graph"
GraphCIDRegex = "^(" + GraphPrefix + "/(" + DefaultUUIDRegex + "))$"
MaintenancePrefix = "/maintenance"
MaintenanceCIDRegex = "^(" + MaintenancePrefix + "/(" + DefaultCIDRegex + "))$"
MetricClusterPrefix = "/metric_cluster"
MetricClusterCIDRegex = "^(" + MetricClusterPrefix + "/(" + DefaultCIDRegex + "))$"
MetricPrefix = "/metric"
MetricCIDRegex = "^(" + MetricPrefix + "/((" + DefaultCIDRegex + ")_([^[:space:]]+)))$"
OutlierReportPrefix = "/outlier_report"
OutlierReportCIDRegex = "^(" + OutlierReportPrefix + "/(" + DefaultCIDRegex + "))$"
ProvisionBrokerPrefix = "/provision_broker"
ProvisionBrokerCIDRegex = "^(" + ProvisionBrokerPrefix + "/([a-z0-9]+-[a-z0-9]+))$"
RuleSetGroupPrefix = "/rule_set_group"
RuleSetGroupCIDRegex = "^(" + RuleSetGroupPrefix + "/(" + DefaultCIDRegex + "))$"
RuleSetPrefix = "/rule_set"
RuleSetCIDRegex = "^(" + RuleSetPrefix + "/((" + DefaultCIDRegex + ")_([^[:space:]]+)))$"
UserPrefix = "/user"
UserCIDRegex = "^(" + UserPrefix + "/(" + DefaultCIDRegex + "|current))$"
WorksheetPrefix = "/worksheet"
WorksheetCIDRegex = "^(" + WorksheetPrefix + "/(" + DefaultUUIDRegex + "))$"
// contact group serverity levels
NumSeverityLevels = 5
)

View File

@ -0,0 +1,263 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Contact Group API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/contact_group
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// ContactGroupAlertFormats define alert formats
type ContactGroupAlertFormats struct {
LongMessage *string `json:"long_message"` // string or null
LongSubject *string `json:"long_subject"` // string or null
LongSummary *string `json:"long_summary"` // string or null
ShortMessage *string `json:"short_message"` // string or null
ShortSummary *string `json:"short_summary"` // string or null
}
// ContactGroupContactsExternal external contacts
type ContactGroupContactsExternal struct {
Info string `json:"contact_info"` // string
Method string `json:"method"` // string
}
// ContactGroupContactsUser user contacts
type ContactGroupContactsUser struct {
Info string `json:"_contact_info,omitempty"` // string
Method string `json:"method"` // string
UserCID string `json:"user"` // string
}
// ContactGroupContacts list of contacts
type ContactGroupContacts struct {
External []ContactGroupContactsExternal `json:"external"` // [] len >= 0
Users []ContactGroupContactsUser `json:"users"` // [] len >= 0
}
// ContactGroupEscalation defines escalations for severity levels
type ContactGroupEscalation struct {
After uint `json:"after"` // uint
ContactGroupCID string `json:"contact_group"` // string
}
// ContactGroup defines a contact group. See https://login.circonus.com/resources/api/calls/contact_group for more information.
type ContactGroup struct {
AggregationWindow uint `json:"aggregation_window,omitempty"` // uint
AlertFormats ContactGroupAlertFormats `json:"alert_formats,omitempty"` // ContactGroupAlertFormats
CID string `json:"_cid,omitempty"` // string
Contacts ContactGroupContacts `json:"contacts,omitempty"` // ContactGroupContacts
Escalations []*ContactGroupEscalation `json:"escalations,omitempty"` // [] len == 5, elements: ContactGroupEscalation or null
LastModified uint `json:"_last_modified,omitempty"` // uint
LastModifiedBy string `json:"_last_modified_by,omitempty"` // string
Name string `json:"name,omitempty"` // string
Reminders []uint `json:"reminders,omitempty"` // [] len == 5
Tags []string `json:"tags,omitempty"` // [] len >= 0
}
// NewContactGroup returns a ContactGroup (with defaults, if applicable)
func NewContactGroup() *ContactGroup {
return &ContactGroup{
Escalations: make([]*ContactGroupEscalation, config.NumSeverityLevels),
Reminders: make([]uint, config.NumSeverityLevels),
Contacts: ContactGroupContacts{
External: []ContactGroupContactsExternal{},
Users: []ContactGroupContactsUser{},
},
}
}
// FetchContactGroup retrieves contact group with passed cid.
func (a *API) FetchContactGroup(cid CIDType) (*ContactGroup, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid contact group CID [none]")
}
groupCID := string(*cid)
matched, err := regexp.MatchString(config.ContactGroupCIDRegex, groupCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid contact group CID [%s]", groupCID)
}
result, err := a.Get(groupCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch contact group, received JSON: %s", string(result))
}
group := new(ContactGroup)
if err := json.Unmarshal(result, group); err != nil {
return nil, err
}
return group, nil
}
// FetchContactGroups retrieves all contact groups available to the API Token.
func (a *API) FetchContactGroups() (*[]ContactGroup, error) {
result, err := a.Get(config.ContactGroupPrefix)
if err != nil {
return nil, err
}
var groups []ContactGroup
if err := json.Unmarshal(result, &groups); err != nil {
return nil, err
}
return &groups, nil
}
// UpdateContactGroup updates passed contact group.
func (a *API) UpdateContactGroup(cfg *ContactGroup) (*ContactGroup, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid contact group config [nil]")
}
groupCID := string(cfg.CID)
matched, err := regexp.MatchString(config.ContactGroupCIDRegex, groupCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid contact group CID [%s]", groupCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update contact group, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(groupCID, jsonCfg)
if err != nil {
return nil, err
}
group := &ContactGroup{}
if err := json.Unmarshal(result, group); err != nil {
return nil, err
}
return group, nil
}
// CreateContactGroup creates a new contact group.
func (a *API) CreateContactGroup(cfg *ContactGroup) (*ContactGroup, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid contact group config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] create contact group, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.ContactGroupPrefix, jsonCfg)
if err != nil {
return nil, err
}
group := &ContactGroup{}
if err := json.Unmarshal(result, group); err != nil {
return nil, err
}
return group, nil
}
// DeleteContactGroup deletes passed contact group.
func (a *API) DeleteContactGroup(cfg *ContactGroup) (bool, error) {
if cfg == nil {
return false, fmt.Errorf("Invalid contact group config [nil]")
}
return a.DeleteContactGroupByCID(CIDType(&cfg.CID))
}
// DeleteContactGroupByCID deletes contact group with passed cid.
func (a *API) DeleteContactGroupByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, fmt.Errorf("Invalid contact group CID [none]")
}
groupCID := string(*cid)
matched, err := regexp.MatchString(config.ContactGroupCIDRegex, groupCID)
if err != nil {
return false, err
}
if !matched {
return false, fmt.Errorf("Invalid contact group CID [%s]", groupCID)
}
_, err = a.Delete(groupCID)
if err != nil {
return false, err
}
return true, nil
}
// SearchContactGroups returns contact groups matching the specified
// search query and/or filter. If nil is passed for both parameters
// all contact groups will be returned.
func (a *API) SearchContactGroups(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]ContactGroup, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchContactGroups()
}
reqURL := url.URL{
Path: config.ContactGroupPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var groups []ContactGroup
if err := json.Unmarshal(result, &groups); err != nil {
return nil, err
}
return &groups, nil
}

View File

@ -0,0 +1,399 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Dashboard API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/dashboard
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// DashboardGridLayout defines layout
type DashboardGridLayout struct {
Height uint `json:"height"`
Width uint `json:"width"`
}
// DashboardAccessConfig defines access config
type DashboardAccessConfig struct {
BlackDash bool `json:"black_dash,omitempty"`
Enabled bool `json:"enabled,omitempty"`
Fullscreen bool `json:"fullscreen,omitempty"`
FullscreenHideTitle bool `json:"fullscreen_hide_title,omitempty"`
Nickname string `json:"nickname,omitempty"`
ScaleText bool `json:"scale_text,omitempty"`
SharedID string `json:"shared_id,omitempty"`
TextSize uint `json:"text_size,omitempty"`
}
// DashboardOptions defines options
type DashboardOptions struct {
AccessConfigs []DashboardAccessConfig `json:"access_configs,omitempty"`
FullscreenHideTitle bool `json:"fullscreen_hide_title,omitempty"`
HideGrid bool `json:"hide_grid,omitempty"`
Linkages [][]string `json:"linkages,omitempty"`
ScaleText bool `json:"scale_text,omitempty"`
TextSize uint `json:"text_size,omitempty"`
}
// ChartTextWidgetDatapoint defines datapoints for charts
type ChartTextWidgetDatapoint struct {
AccountID string `json:"account_id,omitempty"` // metric cluster, metric
CheckID uint `json:"_check_id,omitempty"` // metric
ClusterID uint `json:"cluster_id,omitempty"` // metric cluster
ClusterTitle string `json:"_cluster_title,omitempty"` // metric cluster
Label string `json:"label,omitempty"` // metric
Label2 string `json:"_label,omitempty"` // metric cluster
Metric string `json:"metric,omitempty"` // metric
MetricType string `json:"_metric_type,omitempty"` // metric
NumericOnly bool `json:"numeric_only,omitempty"` // metric cluster
}
// ChartWidgetDefinitionLegend defines chart widget definition legend
type ChartWidgetDefinitionLegend struct {
Show bool `json:"show,omitempty"`
Type string `json:"type,omitempty"`
}
// ChartWidgetWedgeLabels defines chart widget wedge labels
type ChartWidgetWedgeLabels struct {
OnChart bool `json:"on_chart,omitempty"`
ToolTips bool `json:"tooltips,omitempty"`
}
// ChartWidgetWedgeValues defines chart widget wedge values
type ChartWidgetWedgeValues struct {
Angle string `json:"angle,omitempty"`
Color string `json:"color,omitempty"`
Show bool `json:"show,omitempty"`
}
// ChartWidgtDefinition defines chart widget definition
type ChartWidgtDefinition struct {
Datasource string `json:"datasource,omitempty"`
Derive string `json:"derive,omitempty"`
DisableAutoformat bool `json:"disable_autoformat,omitempty"`
Formula string `json:"formula,omitempty"`
Legend ChartWidgetDefinitionLegend `json:"legend,omitempty"`
Period uint `json:"period,omitempty"`
PopOnHover bool `json:"pop_onhover,omitempty"`
WedgeLabels ChartWidgetWedgeLabels `json:"wedge_labels,omitempty"`
WedgeValues ChartWidgetWedgeValues `json:"wedge_values,omitempty"`
}
// ForecastGaugeWidgetThresholds defines forecast widget thresholds
type ForecastGaugeWidgetThresholds struct {
Colors []string `json:"colors,omitempty"` // forecasts, gauges
Flip bool `json:"flip,omitempty"` // gauges
Values []string `json:"values,omitempty"` // forecasts, gauges
}
// StatusWidgetAgentStatusSettings defines agent status settings
type StatusWidgetAgentStatusSettings struct {
Search string `json:"search,omitempty"`
ShowAgentTypes string `json:"show_agent_types,omitempty"`
ShowContact bool `json:"show_contact,omitempty"`
ShowFeeds bool `json:"show_feeds,omitempty"`
ShowSetup bool `json:"show_setup,omitempty"`
ShowSkew bool `json:"show_skew,omitempty"`
ShowUpdates bool `json:"show_updates,omitempty"`
}
// StatusWidgetHostStatusSettings defines host status settings
type StatusWidgetHostStatusSettings struct {
LayoutStyle string `json:"layout_style,omitempty"`
Search string `json:"search,omitempty"`
SortBy string `json:"sort_by,omitempty"`
TagFilterSet []string `json:"tag_filter_set,omitempty"`
}
// DashboardWidgetSettings defines settings specific to widget
type DashboardWidgetSettings struct {
AccountID string `json:"account_id,omitempty"` // alerts, clusters, gauges, graphs, lists, status
Acknowledged string `json:"acknowledged,omitempty"` // alerts
AgentStatusSettings StatusWidgetAgentStatusSettings `json:"agent_status_settings,omitempty"` // status
Algorithm string `json:"algorithm,omitempty"` // clusters
Autoformat bool `json:"autoformat,omitempty"` // text
BodyFormat string `json:"body_format,omitempty"` // text
ChartType string `json:"chart_type,omitempty"` // charts
CheckUUID string `json:"check_uuid,omitempty"` // gauges
Cleared string `json:"cleared,omitempty"` // alerts
ClusterID uint `json:"cluster_id,omitempty"` // clusters
ClusterName string `json:"cluster_name,omitempty"` // clusters
ContactGroups []uint `json:"contact_groups,omitempty"` // alerts
ContentType string `json:"content_type,omitempty"` // status
Datapoints []ChartTextWidgetDatapoint `json:"datapoints,omitempty"` // charts, text
DateWindow string `json:"date_window,omitempty"` // graphs
Definition ChartWidgtDefinition `json:"definition,omitempty"` // charts
Dependents string `json:"dependents,omitempty"` // alerts
DisableAutoformat bool `json:"disable_autoformat,omitempty"` // gauges
Display string `json:"display,omitempty"` // alerts
Format string `json:"format,omitempty"` // forecasts
Formula string `json:"formula,omitempty"` // gauges
GraphUUID string `json:"graph_id,omitempty"` // graphs
HideXAxis bool `json:"hide_xaxis,omitempty"` // graphs
HideYAxis bool `json:"hide_yaxis,omitempty"` // graphs
HostStatusSettings StatusWidgetHostStatusSettings `json:"host_status_settings,omitempty"` // status
KeyInline bool `json:"key_inline,omitempty"` // graphs
KeyLoc string `json:"key_loc,omitempty"` // graphs
KeySize uint `json:"key_size,omitempty"` // graphs
KeyWrap bool `json:"key_wrap,omitempty"` // graphs
Label string `json:"label,omitempty"` // graphs
Layout string `json:"layout,omitempty"` // clusters
Limit uint `json:"limit,omitempty"` // lists
Maintenance string `json:"maintenance,omitempty"` // alerts
Markup string `json:"markup,omitempty"` // html
MetricDisplayName string `json:"metric_display_name,omitempty"` // gauges
MetricName string `json:"metric_name,omitempty"` // gauges
MinAge string `json:"min_age,omitempty"` // alerts
OffHours []uint `json:"off_hours,omitempty"` // alerts
OverlaySetID string `json:"overlay_set_id,omitempty"` // graphs
Period uint `json:"period,omitempty"` // gauges, text, graphs
RangeHigh int `json:"range_high,omitempty"` // gauges
RangeLow int `json:"range_low,omitempty"` // gauges
Realtime bool `json:"realtime,omitempty"` // graphs
ResourceLimit string `json:"resource_limit,omitempty"` // forecasts
ResourceUsage string `json:"resource_usage,omitempty"` // forecasts
Search string `json:"search,omitempty"` // alerts, lists
Severity string `json:"severity,omitempty"` // alerts
ShowFlags bool `json:"show_flags,omitempty"` // graphs
Size string `json:"size,omitempty"` // clusters
TagFilterSet []string `json:"tag_filter_set,omitempty"` // alerts
Threshold float32 `json:"threshold,omitempty"` // clusters
Thresholds ForecastGaugeWidgetThresholds `json:"thresholds,omitempty"` // forecasts, gauges
TimeWindow string `json:"time_window,omitempty"` // alerts
Title string `json:"title,omitempty"` // alerts, charts, forecasts, gauges, html
TitleFormat string `json:"title_format,omitempty"` // text
Trend string `json:"trend,omitempty"` // forecasts
Type string `json:"type,omitempty"` // gauges, lists
UseDefault bool `json:"use_default,omitempty"` // text
ValueType string `json:"value_type,omitempty"` // gauges, text
WeekDays []string `json:"weekdays,omitempty"` // alerts
}
// DashboardWidget defines widget
type DashboardWidget struct {
Active bool `json:"active"`
Height uint `json:"height"`
Name string `json:"name"`
Origin string `json:"origin"`
Settings DashboardWidgetSettings `json:"settings"`
Type string `json:"type"`
WidgetID string `json:"widget_id"`
Width uint `json:"width"`
}
// Dashboard defines a dashboard. See https://login.circonus.com/resources/api/calls/dashboard for more information.
type Dashboard struct {
AccountDefault bool `json:"account_default"`
Active bool `json:"_active,omitempty"`
CID string `json:"_cid,omitempty"`
Created uint `json:"_created,omitempty"`
CreatedBy string `json:"_created_by,omitempty"`
GridLayout DashboardGridLayout `json:"grid_layout"`
LastModified uint `json:"_last_modified,omitempty"`
Options DashboardOptions `json:"options"`
Shared bool `json:"shared"`
Title string `json:"title"`
UUID string `json:"_dashboard_uuid,omitempty"`
Widgets []DashboardWidget `json:"widgets"`
}
// NewDashboard returns a new Dashboard (with defaults, if applicable)
func NewDashboard() *Dashboard {
return &Dashboard{}
}
// FetchDashboard retrieves dashboard with passed cid.
func (a *API) FetchDashboard(cid CIDType) (*Dashboard, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid dashboard CID [none]")
}
dashboardCID := string(*cid)
matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
}
result, err := a.Get(string(*cid))
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch dashboard, received JSON: %s", string(result))
}
dashboard := new(Dashboard)
if err := json.Unmarshal(result, dashboard); err != nil {
return nil, err
}
return dashboard, nil
}
// FetchDashboards retrieves all dashboards available to the API Token.
func (a *API) FetchDashboards() (*[]Dashboard, error) {
result, err := a.Get(config.DashboardPrefix)
if err != nil {
return nil, err
}
var dashboards []Dashboard
if err := json.Unmarshal(result, &dashboards); err != nil {
return nil, err
}
return &dashboards, nil
}
// UpdateDashboard updates passed dashboard.
func (a *API) UpdateDashboard(cfg *Dashboard) (*Dashboard, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid dashboard config [nil]")
}
dashboardCID := string(cfg.CID)
matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update dashboard, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(dashboardCID, jsonCfg)
if err != nil {
return nil, err
}
dashboard := &Dashboard{}
if err := json.Unmarshal(result, dashboard); err != nil {
return nil, err
}
return dashboard, nil
}
// CreateDashboard creates a new dashboard.
func (a *API) CreateDashboard(cfg *Dashboard) (*Dashboard, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid dashboard config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] create dashboard, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.DashboardPrefix, jsonCfg)
if err != nil {
return nil, err
}
dashboard := &Dashboard{}
if err := json.Unmarshal(result, dashboard); err != nil {
return nil, err
}
return dashboard, nil
}
// DeleteDashboard deletes passed dashboard.
func (a *API) DeleteDashboard(cfg *Dashboard) (bool, error) {
if cfg == nil {
return false, fmt.Errorf("Invalid dashboard config [nil]")
}
return a.DeleteDashboardByCID(CIDType(&cfg.CID))
}
// DeleteDashboardByCID deletes dashboard with passed cid.
func (a *API) DeleteDashboardByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, fmt.Errorf("Invalid dashboard CID [none]")
}
dashboardCID := string(*cid)
matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
if err != nil {
return false, err
}
if !matched {
return false, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
}
_, err = a.Delete(dashboardCID)
if err != nil {
return false, err
}
return true, nil
}
// SearchDashboards returns dashboards matching the specified
// search query and/or filter. If nil is passed for both parameters
// all dashboards will be returned.
func (a *API) SearchDashboards(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Dashboard, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchDashboards()
}
reqURL := url.URL{
Path: config.DashboardPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var dashboards []Dashboard
if err := json.Unmarshal(result, &dashboards); err != nil {
return nil, err
}
return &dashboards, nil
}

View File

@ -0,0 +1,63 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package api provides methods for interacting with the Circonus API. See the full Circonus API
Documentation at https://login.circonus.com/resources/api for more information.
Raw REST methods
Get - retrieve existing item(s)
Put - update an existing item
Post - create a new item
Delete - remove an existing item
Endpoints (supported)
Account https://login.circonus.com/resources/api/calls/account
Acknowledgement https://login.circonus.com/resources/api/calls/acknowledgement
Alert https://login.circonus.com/resources/api/calls/alert
Annotation https://login.circonus.com/resources/api/calls/annotation
Broker https://login.circonus.com/resources/api/calls/broker
Check https://login.circonus.com/resources/api/calls/check
Check Bundle https://login.circonus.com/resources/api/calls/check_bundle
Check Bundle Metrics https://login.circonus.com/resources/api/calls/check_bundle_metrics
Contact Group https://login.circonus.com/resources/api/calls/contact_group
Dashboard https://login.circonus.com/resources/api/calls/dashboard
Graph https://login.circonus.com/resources/api/calls/graph
Maintenance [window] https://login.circonus.com/resources/api/calls/maintenance
Metric https://login.circonus.com/resources/api/calls/metric
Metric Cluster https://login.circonus.com/resources/api/calls/metric_cluster
Outlier Report https://login.circonus.com/resources/api/calls/outlier_report
Provision Broker https://login.circonus.com/resources/api/calls/provision_broker
Rule Set https://login.circonus.com/resources/api/calls/rule_set
Rule Set Group https://login.circonus.com/resources/api/calls/rule_set_group
User https://login.circonus.com/resources/api/calls/user
Worksheet https://login.circonus.com/resources/api/calls/worksheet
Endpoints (not supported)
Support may be added for these endpoints in the future. These endpoints may currently be used
directly with the Raw REST methods above.
CAQL https://login.circonus.com/resources/api/calls/caql
Check Move https://login.circonus.com/resources/api/calls/check_move
Data https://login.circonus.com/resources/api/calls/data
Snapshot https://login.circonus.com/resources/api/calls/snapshot
Tag https://login.circonus.com/resources/api/calls/tag
Template https://login.circonus.com/resources/api/calls/template
Verbs
Fetch singular/plural item(s) - e.g. FetchAnnotation, FetchAnnotations
Create create new item - e.g. CreateAnnotation
Update update an item - e.g. UpdateAnnotation
Delete remove an item - e.g. DeleteAnnotation, DeleteAnnotationByCID
Search search for item(s) - e.g. SearchAnnotations
New new item config - e.g. NewAnnotation (returns an empty item,
any applicable defautls defined)
Not all endpoints support all verbs.
*/
package api

View File

@ -0,0 +1,349 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Graph API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/graph
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// GraphAccessKey defines an access key for a graph
type GraphAccessKey struct {
Active bool `json:"active,omitempty"` // boolean
Height uint `json:"height,omitempty"` // uint
Key string `json:"key,omitempty"` // string
Legend bool `json:"legend,omitempty"` // boolean
LockDate bool `json:"lock_date,omitempty"` // boolean
LockMode string `json:"lock_mode,omitempty"` // string
LockRangeEnd uint `json:"lock_range_end,omitempty"` // uint
LockRangeStart uint `json:"lock_range_start,omitempty"` // uint
LockShowTimes bool `json:"lock_show_times,omitempty"` // boolean
LockZoom string `json:"lock_zoom,omitempty"` // string
Nickname string `json:"nickname,omitempty"` // string
Title bool `json:"title,omitempty"` // boolean
Width uint `json:"width,omitempty"` // uint
XLabels bool `json:"x_labels,omitempty"` // boolean
YLabels bool `json:"y_labels,omitempty"` // boolean
}
// GraphComposite defines a composite
type GraphComposite struct {
Axis string `json:"axis,omitempty"` // string
Color string `json:"color,omitempty"` // string
DataFormula *string `json:"data_formula,omitempty"` // string or null
Hidden bool `json:"hidden,omitempty"` // boolean
LegendFormula *string `json:"legend_formula,omitempty"` // string or null
Name string `json:"name,omitempty"` // string
Stack *uint `json:"stack,omitempty"` // uint or null
}
// GraphDatapoint defines a datapoint
type GraphDatapoint struct {
Alpha *float64 `json:"alpha,string,omitempty"` // float64
Axis string `json:"axis,omitempty"` // string
CAQL *string `json:"caql,omitempty"` // string or null
CheckID uint `json:"check_id,omitempty"` // uint
Color *string `json:"color,omitempty"` // string
DataFormula *string `json:"data_formula"` // string or null
Derive interface{} `json:"derive,omitempty"` // BUG doc: string, api: string or boolean(for caql statements)
Hidden bool `json:"hidden"` // boolean
LegendFormula *string `json:"legend_formula"` // string or null
MetricName string `json:"metric_name,omitempty"` // string
MetricType string `json:"metric_type,omitempty"` // string
Name string `json:"name"` // string
Stack *uint `json:"stack"` // uint or null
}
// GraphGuide defines a guide
type GraphGuide struct {
Color string `json:"color,omitempty"` // string
DataFormula *string `json:"data_formula,omitempty"` // string or null
Hidden bool `json:"hidden,omitempty"` // boolean
LegendFormula *string `json:"legend_formula,omitempty"` // string or null
Name string `json:"name,omitempty"` // string
}
// GraphMetricCluster defines a metric cluster
type GraphMetricCluster struct {
AggregateFunc string `json:"aggregate_function,omitempty"` // string
Axis string `json:"axis,omitempty"` // string
Color *string `json:"color,omitempty"` // string
DataFormula *string `json:"data_formula"` // string or null
Hidden bool `json:"hidden"` // boolean
LegendFormula *string `json:"legend_formula"` // string or null
MetricCluster string `json:"metric_cluster,omitempty"` // string
Name string `json:"name,omitempty"` // string
Stack *uint `json:"stack"` // uint or null
}
// OverlayDataOptions defines overlay options for data. Note, each overlay type requires
// a _subset_ of the options. See Graph API documentation (URL above) for details.
type OverlayDataOptions struct {
Alerts *int `json:"alerts,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
ArrayOutput *int `json:"array_output,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
BasePeriod *int `json:"base_period,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
Delay *int `json:"delay,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
Extension string `json:"extension,omitempty"` // string
GraphTitle string `json:"graph_title,omitempty"` // string
GraphUUID string `json:"graph_id,omitempty"` // string
InPercent *bool `json:"in_percent,string,omitempty"` // boolean encoded as string BUG doc: boolean, api: string
Inverse *int `json:"inverse,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
Method string `json:"method,omitempty"` // string
Model string `json:"model,omitempty"` // string
ModelEnd string `json:"model_end,omitempty"` // string
ModelPeriod string `json:"model_period,omitempty"` // string
ModelRelative *int `json:"model_relative,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
Out string `json:"out,omitempty"` // string
Prequel string `json:"prequel,omitempty"` // string
Presets string `json:"presets,omitempty"` // string
Quantiles string `json:"quantiles,omitempty"` // string
SeasonLength *int `json:"season_length,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
Sensitivity *int `json:"sensitivity,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
SingleValue *int `json:"single_value,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
TargetPeriod string `json:"target_period,omitempty"` // string
TimeOffset string `json:"time_offset,omitempty"` // string
TimeShift *int `json:"time_shift,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
Transform string `json:"transform,omitempty"` // string
Version *int `json:"version,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
Window *int `json:"window,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
XShift string `json:"x_shift,omitempty"` // string
}
// OverlayUISpecs defines UI specs for overlay
type OverlayUISpecs struct {
Decouple bool `json:"decouple,omitempty"` // boolean
ID string `json:"id,omitempty"` // string
Label string `json:"label,omitempty"` // string
Type string `json:"type,omitempty"` // string
Z *int `json:"z,string,omitempty"` // int encoded as string BUG doc: numeric, api: string
}
// GraphOverlaySet defines overlays for graph
type GraphOverlaySet struct {
DataOpts OverlayDataOptions `json:"data_opts,omitempty"` // OverlayDataOptions
ID string `json:"id,omitempty"` // string
Title string `json:"title,omitempty"` // string
UISpecs OverlayUISpecs `json:"ui_specs,omitempty"` // OverlayUISpecs
}
// Graph defines a graph. See https://login.circonus.com/resources/api/calls/graph for more information.
type Graph struct {
AccessKeys []GraphAccessKey `json:"access_keys,omitempty"` // [] len >= 0
CID string `json:"_cid,omitempty"` // string
Composites []GraphComposite `json:"composites,omitempty"` // [] len >= 0
Datapoints []GraphDatapoint `json:"datapoints,omitempt"` // [] len >= 0
Description string `json:"description,omitempty"` // string
Guides []GraphGuide `json:"guides,omitempty"` // [] len >= 0
LineStyle *string `json:"line_style"` // string or null
LogLeftY *int `json:"logarithmic_left_y,string,omitempty"` // int encoded as string or null BUG doc: number (not string)
LogRightY *int `json:"logarithmic_right_y,string,omitempty"` // int encoded as string or null BUG doc: number (not string)
MaxLeftY *float64 `json:"max_left_y,string,omitempty"` // float64 encoded as string or null BUG doc: number (not string)
MaxRightY *float64 `json:"max_right_y,string,omitempty"` // float64 encoded as string or null BUG doc: number (not string)
MetricClusters []GraphMetricCluster `json:"metric_clusters,omitempty"` // [] len >= 0
MinLeftY *float64 `json:"min_left_y,string,omitempty"` // float64 encoded as string or null BUG doc: number (not string)
MinRightY *float64 `json:"min_right_y,string,omitempty"` // float64 encoded as string or null BUG doc: number (not string)
Notes *string `json:"notes,omitempty"` // string or null
OverlaySets *map[string]GraphOverlaySet `json:"overlay_sets,omitempty"` // GroupOverLaySets or null
Style *string `json:"style"` // string or null
Tags []string `json:"tags,omitempty"` // [] len >= 0
Title string `json:"title,omitempty"` // string
}
// NewGraph returns a Graph (with defaults, if applicable)
func NewGraph() *Graph {
return &Graph{}
}
// FetchGraph retrieves graph with passed cid.
func (a *API) FetchGraph(cid CIDType) (*Graph, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid graph CID [none]")
}
graphCID := string(*cid)
matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid graph CID [%s]", graphCID)
}
result, err := a.Get(graphCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch graph, received JSON: %s", string(result))
}
graph := new(Graph)
if err := json.Unmarshal(result, graph); err != nil {
return nil, err
}
return graph, nil
}
// FetchGraphs retrieves all graphs available to the API Token.
func (a *API) FetchGraphs() (*[]Graph, error) {
result, err := a.Get(config.GraphPrefix)
if err != nil {
return nil, err
}
var graphs []Graph
if err := json.Unmarshal(result, &graphs); err != nil {
return nil, err
}
return &graphs, nil
}
// UpdateGraph updates passed graph.
func (a *API) UpdateGraph(cfg *Graph) (*Graph, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid graph config [nil]")
}
graphCID := string(cfg.CID)
matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid graph CID [%s]", graphCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update graph, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(graphCID, jsonCfg)
if err != nil {
return nil, err
}
graph := &Graph{}
if err := json.Unmarshal(result, graph); err != nil {
return nil, err
}
return graph, nil
}
// CreateGraph creates a new graph.
func (a *API) CreateGraph(cfg *Graph) (*Graph, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid graph config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update graph, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.GraphPrefix, jsonCfg)
if err != nil {
return nil, err
}
graph := &Graph{}
if err := json.Unmarshal(result, graph); err != nil {
return nil, err
}
return graph, nil
}
// DeleteGraph deletes passed graph.
func (a *API) DeleteGraph(cfg *Graph) (bool, error) {
if cfg == nil {
return false, fmt.Errorf("Invalid graph config [nil]")
}
return a.DeleteGraphByCID(CIDType(&cfg.CID))
}
// DeleteGraphByCID deletes graph with passed cid.
func (a *API) DeleteGraphByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, fmt.Errorf("Invalid graph CID [none]")
}
graphCID := string(*cid)
matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID)
if err != nil {
return false, err
}
if !matched {
return false, fmt.Errorf("Invalid graph CID [%s]", graphCID)
}
_, err = a.Delete(graphCID)
if err != nil {
return false, err
}
return true, nil
}
// SearchGraphs returns graphs matching the specified search query
// and/or filter. If nil is passed for both parameters all graphs
// will be returned.
func (a *API) SearchGraphs(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Graph, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchGraphs()
}
reqURL := url.URL{
Path: config.GraphPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var graphs []Graph
if err := json.Unmarshal(result, &graphs); err != nil {
return nil, err
}
return &graphs, nil
}

View File

@ -0,0 +1,220 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Maintenance window API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/maintenance
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// Maintenance defines a maintenance window. See https://login.circonus.com/resources/api/calls/maintenance for more information.
type Maintenance struct {
CID string `json:"_cid,omitempty"` // string
Item string `json:"item,omitempty"` // string
Notes string `json:"notes,omitempty"` // string
Severities interface{} `json:"severities,omitempty"` // []string NOTE can be set with CSV string or []string
Start uint `json:"start,omitempty"` // uint
Stop uint `json:"stop,omitempty"` // uint
Tags []string `json:"tags,omitempty"` // [] len >= 0
Type string `json:"type,omitempty"` // string
}
// NewMaintenanceWindow returns a new Maintenance window (with defaults, if applicable)
func NewMaintenanceWindow() *Maintenance {
return &Maintenance{}
}
// FetchMaintenanceWindow retrieves maintenance [window] with passed cid.
func (a *API) FetchMaintenanceWindow(cid CIDType) (*Maintenance, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid maintenance window CID [none]")
}
maintenanceCID := string(*cid)
matched, err := regexp.MatchString(config.MaintenanceCIDRegex, maintenanceCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid maintenance window CID [%s]", maintenanceCID)
}
result, err := a.Get(maintenanceCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch maintenance window, received JSON: %s", string(result))
}
window := &Maintenance{}
if err := json.Unmarshal(result, window); err != nil {
return nil, err
}
return window, nil
}
// FetchMaintenanceWindows retrieves all maintenance [windows] available to API Token.
func (a *API) FetchMaintenanceWindows() (*[]Maintenance, error) {
result, err := a.Get(config.MaintenancePrefix)
if err != nil {
return nil, err
}
var windows []Maintenance
if err := json.Unmarshal(result, &windows); err != nil {
return nil, err
}
return &windows, nil
}
// UpdateMaintenanceWindow updates passed maintenance [window].
func (a *API) UpdateMaintenanceWindow(cfg *Maintenance) (*Maintenance, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid maintenance window config [nil]")
}
maintenanceCID := string(cfg.CID)
matched, err := regexp.MatchString(config.MaintenanceCIDRegex, maintenanceCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid maintenance window CID [%s]", maintenanceCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update maintenance window, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(maintenanceCID, jsonCfg)
if err != nil {
return nil, err
}
window := &Maintenance{}
if err := json.Unmarshal(result, window); err != nil {
return nil, err
}
return window, nil
}
// CreateMaintenanceWindow creates a new maintenance [window].
func (a *API) CreateMaintenanceWindow(cfg *Maintenance) (*Maintenance, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid maintenance window config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] create maintenance window, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.MaintenancePrefix, jsonCfg)
if err != nil {
return nil, err
}
window := &Maintenance{}
if err := json.Unmarshal(result, window); err != nil {
return nil, err
}
return window, nil
}
// DeleteMaintenanceWindow deletes passed maintenance [window].
func (a *API) DeleteMaintenanceWindow(cfg *Maintenance) (bool, error) {
if cfg == nil {
return false, fmt.Errorf("Invalid maintenance window config [nil]")
}
return a.DeleteMaintenanceWindowByCID(CIDType(&cfg.CID))
}
// DeleteMaintenanceWindowByCID deletes maintenance [window] with passed cid.
func (a *API) DeleteMaintenanceWindowByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, fmt.Errorf("Invalid maintenance window CID [none]")
}
maintenanceCID := string(*cid)
matched, err := regexp.MatchString(config.MaintenanceCIDRegex, maintenanceCID)
if err != nil {
return false, err
}
if !matched {
return false, fmt.Errorf("Invalid maintenance window CID [%s]", maintenanceCID)
}
_, err = a.Delete(maintenanceCID)
if err != nil {
return false, err
}
return true, nil
}
// SearchMaintenanceWindows returns maintenance [windows] matching
// the specified search query and/or filter. If nil is passed for
// both parameters all maintenance [windows] will be returned.
func (a *API) SearchMaintenanceWindows(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Maintenance, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchMaintenanceWindows()
}
reqURL := url.URL{
Path: config.MaintenancePrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var windows []Maintenance
if err := json.Unmarshal(result, &windows); err != nil {
return nil, err
}
return &windows, nil
}

View File

@ -0,0 +1,162 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Metric API support - Fetch, Create*, Update, Delete*, and Search
// See: https://login.circonus.com/resources/api/calls/metric
// * : create and delete are handled via check_bundle or check_bundle_metrics
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// Metric defines a metric. See https://login.circonus.com/resources/api/calls/metric for more information.
type Metric struct {
Active bool `json:"_active,omitempty"` // boolean
CheckActive bool `json:"_check_active,omitempty"` // boolean
CheckBundleCID string `json:"_check_bundle,omitempty"` // string
CheckCID string `json:"_check,omitempty"` // string
CheckTags []string `json:"_check_tags,omitempty"` // [] len >= 0
CheckUUID string `json:"_check_uuid,omitempty"` // string
CID string `json:"_cid,omitempty"` // string
Histogram string `json:"_histogram,omitempty"` // string
Link *string `json:"link,omitempty"` // string or null
MetricName string `json:"_metric_name,omitempty"` // string
MetricType string `json:"_metric_type,omitempty"` // string
Notes *string `json:"notes,omitempty"` // string or null
Tags []string `json:"tags,omitempty"` // [] len >= 0
Units *string `json:"units,omitempty"` // string or null
}
// FetchMetric retrieves metric with passed cid.
func (a *API) FetchMetric(cid CIDType) (*Metric, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid metric CID [none]")
}
metricCID := string(*cid)
matched, err := regexp.MatchString(config.MetricCIDRegex, metricCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid metric CID [%s]", metricCID)
}
result, err := a.Get(metricCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch metric, received JSON: %s", string(result))
}
metric := &Metric{}
if err := json.Unmarshal(result, metric); err != nil {
return nil, err
}
return metric, nil
}
// FetchMetrics retrieves all metrics available to API Token.
func (a *API) FetchMetrics() (*[]Metric, error) {
result, err := a.Get(config.MetricPrefix)
if err != nil {
return nil, err
}
var metrics []Metric
if err := json.Unmarshal(result, &metrics); err != nil {
return nil, err
}
return &metrics, nil
}
// UpdateMetric updates passed metric.
func (a *API) UpdateMetric(cfg *Metric) (*Metric, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid metric config [nil]")
}
metricCID := string(cfg.CID)
matched, err := regexp.MatchString(config.MetricCIDRegex, metricCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid metric CID [%s]", metricCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update metric, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(metricCID, jsonCfg)
if err != nil {
return nil, err
}
metric := &Metric{}
if err := json.Unmarshal(result, metric); err != nil {
return nil, err
}
return metric, nil
}
// SearchMetrics returns metrics matching the specified search query
// and/or filter. If nil is passed for both parameters all metrics
// will be returned.
func (a *API) SearchMetrics(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Metric, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchMetrics()
}
reqURL := url.URL{
Path: config.MetricPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var metrics []Metric
if err := json.Unmarshal(result, &metrics); err != nil {
return nil, err
}
return &metrics, nil
}

View File

@ -0,0 +1,261 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Metric Cluster API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/metric_cluster
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// MetricQuery object
type MetricQuery struct {
Query string `json:"query"`
Type string `json:"type"`
}
// MetricCluster defines a metric cluster. See https://login.circonus.com/resources/api/calls/metric_cluster for more information.
type MetricCluster struct {
CID string `json:"_cid,omitempty"` // string
Description string `json:"description"` // string
MatchingMetrics []string `json:"_matching_metrics,omitempty"` // [] len >= 1 (result info only, if query has extras - cannot be set)
MatchingUUIDMetrics map[string][]string `json:"_matching_uuid_metrics,omitempty"` // [] len >= 1 (result info only, if query has extras - cannot be set)
Name string `json:"name"` // string
Queries []MetricQuery `json:"queries"` // [] len >= 1
Tags []string `json:"tags"` // [] len >= 0
}
// NewMetricCluster returns a new MetricCluster (with defaults, if applicable)
func NewMetricCluster() *MetricCluster {
return &MetricCluster{}
}
// FetchMetricCluster retrieves metric cluster with passed cid.
func (a *API) FetchMetricCluster(cid CIDType, extras string) (*MetricCluster, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid metric cluster CID [none]")
}
clusterCID := string(*cid)
matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID)
}
reqURL := url.URL{
Path: clusterCID,
}
extra := ""
switch extras {
case "metrics":
extra = "_matching_metrics"
case "uuids":
extra = "_matching_uuid_metrics"
}
if extra != "" {
q := url.Values{}
q.Set("extra", extra)
reqURL.RawQuery = q.Encode()
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch metric cluster, received JSON: %s", string(result))
}
cluster := &MetricCluster{}
if err := json.Unmarshal(result, cluster); err != nil {
return nil, err
}
return cluster, nil
}
// FetchMetricClusters retrieves all metric clusters available to API Token.
func (a *API) FetchMetricClusters(extras string) (*[]MetricCluster, error) {
reqURL := url.URL{
Path: config.MetricClusterPrefix,
}
extra := ""
switch extras {
case "metrics":
extra = "_matching_metrics"
case "uuids":
extra = "_matching_uuid_metrics"
}
if extra != "" {
q := url.Values{}
q.Set("extra", extra)
reqURL.RawQuery = q.Encode()
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, err
}
var clusters []MetricCluster
if err := json.Unmarshal(result, &clusters); err != nil {
return nil, err
}
return &clusters, nil
}
// UpdateMetricCluster updates passed metric cluster.
func (a *API) UpdateMetricCluster(cfg *MetricCluster) (*MetricCluster, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid metric cluster config [nil]")
}
clusterCID := string(cfg.CID)
matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update metric cluster, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(clusterCID, jsonCfg)
if err != nil {
return nil, err
}
cluster := &MetricCluster{}
if err := json.Unmarshal(result, cluster); err != nil {
return nil, err
}
return cluster, nil
}
// CreateMetricCluster creates a new metric cluster.
func (a *API) CreateMetricCluster(cfg *MetricCluster) (*MetricCluster, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid metric cluster config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] create metric cluster, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.MetricClusterPrefix, jsonCfg)
if err != nil {
return nil, err
}
cluster := &MetricCluster{}
if err := json.Unmarshal(result, cluster); err != nil {
return nil, err
}
return cluster, nil
}
// DeleteMetricCluster deletes passed metric cluster.
func (a *API) DeleteMetricCluster(cfg *MetricCluster) (bool, error) {
if cfg == nil {
return false, fmt.Errorf("Invalid metric cluster config [nil]")
}
return a.DeleteMetricClusterByCID(CIDType(&cfg.CID))
}
// DeleteMetricClusterByCID deletes metric cluster with passed cid.
func (a *API) DeleteMetricClusterByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, fmt.Errorf("Invalid metric cluster CID [none]")
}
clusterCID := string(*cid)
matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID)
if err != nil {
return false, err
}
if !matched {
return false, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID)
}
_, err = a.Delete(clusterCID)
if err != nil {
return false, err
}
return true, nil
}
// SearchMetricClusters returns metric clusters matching the specified
// search query and/or filter. If nil is passed for both parameters
// all metric clusters will be returned.
func (a *API) SearchMetricClusters(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]MetricCluster, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchMetricClusters("")
}
reqURL := url.URL{
Path: config.MetricClusterPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var clusters []MetricCluster
if err := json.Unmarshal(result, &clusters); err != nil {
return nil, err
}
return &clusters, nil
}

View File

@ -0,0 +1,221 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// OutlierReport API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/report
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// OutlierReport defines a outlier report. See https://login.circonus.com/resources/api/calls/report for more information.
type OutlierReport struct {
CID string `json:"_cid,omitempty"` // string
Config string `json:"config,omitempty"` // string
Created uint `json:"_created,omitempty"` // uint
CreatedBy string `json:"_created_by,omitempty"` // string
LastModified uint `json:"_last_modified,omitempty"` // uint
LastModifiedBy string `json:"_last_modified_by,omitempty"` // string
MetricClusterCID string `json:"metric_cluster,omitempty"` // st ring
Tags []string `json:"tags,omitempty"` // [] len >= 0
Title string `json:"title,omitempty"` // string
}
// NewOutlierReport returns a new OutlierReport (with defaults, if applicable)
func NewOutlierReport() *OutlierReport {
return &OutlierReport{}
}
// FetchOutlierReport retrieves outlier report with passed cid.
func (a *API) FetchOutlierReport(cid CIDType) (*OutlierReport, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid outlier report CID [none]")
}
reportCID := string(*cid)
matched, err := regexp.MatchString(config.OutlierReportCIDRegex, reportCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid outlier report CID [%s]", reportCID)
}
result, err := a.Get(reportCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch outlier report, received JSON: %s", string(result))
}
report := &OutlierReport{}
if err := json.Unmarshal(result, report); err != nil {
return nil, err
}
return report, nil
}
// FetchOutlierReports retrieves all outlier reports available to API Token.
func (a *API) FetchOutlierReports() (*[]OutlierReport, error) {
result, err := a.Get(config.OutlierReportPrefix)
if err != nil {
return nil, err
}
var reports []OutlierReport
if err := json.Unmarshal(result, &reports); err != nil {
return nil, err
}
return &reports, nil
}
// UpdateOutlierReport updates passed outlier report.
func (a *API) UpdateOutlierReport(cfg *OutlierReport) (*OutlierReport, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid outlier report config [nil]")
}
reportCID := string(cfg.CID)
matched, err := regexp.MatchString(config.OutlierReportCIDRegex, reportCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid outlier report CID [%s]", reportCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update outlier report, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(reportCID, jsonCfg)
if err != nil {
return nil, err
}
report := &OutlierReport{}
if err := json.Unmarshal(result, report); err != nil {
return nil, err
}
return report, nil
}
// CreateOutlierReport creates a new outlier report.
func (a *API) CreateOutlierReport(cfg *OutlierReport) (*OutlierReport, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid outlier report config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] create outlier report, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.OutlierReportPrefix, jsonCfg)
if err != nil {
return nil, err
}
report := &OutlierReport{}
if err := json.Unmarshal(result, report); err != nil {
return nil, err
}
return report, nil
}
// DeleteOutlierReport deletes passed outlier report.
func (a *API) DeleteOutlierReport(cfg *OutlierReport) (bool, error) {
if cfg == nil {
return false, fmt.Errorf("Invalid outlier report config [nil]")
}
return a.DeleteOutlierReportByCID(CIDType(&cfg.CID))
}
// DeleteOutlierReportByCID deletes outlier report with passed cid.
func (a *API) DeleteOutlierReportByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, fmt.Errorf("Invalid outlier report CID [none]")
}
reportCID := string(*cid)
matched, err := regexp.MatchString(config.OutlierReportCIDRegex, reportCID)
if err != nil {
return false, err
}
if !matched {
return false, fmt.Errorf("Invalid outlier report CID [%s]", reportCID)
}
_, err = a.Delete(reportCID)
if err != nil {
return false, err
}
return true, nil
}
// SearchOutlierReports returns outlier report matching the
// specified search query and/or filter. If nil is passed for
// both parameters all outlier report will be returned.
func (a *API) SearchOutlierReports(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]OutlierReport, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchOutlierReports()
}
reqURL := url.URL{
Path: config.OutlierReportPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var reports []OutlierReport
if err := json.Unmarshal(result, &reports); err != nil {
return nil, err
}
return &reports, nil
}

View File

@ -0,0 +1,151 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// ProvisionBroker API support - Fetch, Create, and Update
// See: https://login.circonus.com/resources/api/calls/provision_broker
// Note that the provision_broker endpoint does not return standard cid format
// of '/object/item' (e.g. /provision_broker/abc-123) it just returns 'item'
package api
import (
"encoding/json"
"fmt"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// BrokerStratcon defines stratcons for broker
type BrokerStratcon struct {
CN string `json:"cn,omitempty"` // string
Host string `json:"host,omitempty"` // string
Port string `json:"port,omitempty"` // string
}
// ProvisionBroker defines a provision broker [request]. See https://login.circonus.com/resources/api/calls/provision_broker for more details.
type ProvisionBroker struct {
Cert string `json:"_cert,omitempty"` // string
CID string `json:"_cid,omitempty"` // string
CSR string `json:"_csr,omitempty"` // string
ExternalHost string `json:"external_host,omitempty"` // string
ExternalPort string `json:"external_port,omitempty"` // string
IPAddress string `json:"ipaddress,omitempty"` // string
Latitude string `json:"latitude,omitempty"` // string
Longitude string `json:"longitude,omitempty"` // string
Name string `json:"noit_name,omitempty"` // string
Port string `json:"port,omitempty"` // string
PreferReverseConnection bool `json:"prefer_reverse_connection,omitempty"` // boolean
Rebuild bool `json:"rebuild,omitempty"` // boolean
Stratcons []BrokerStratcon `json:"_stratcons,omitempty"` // [] len >= 1
Tags []string `json:"tags,omitempty"` // [] len >= 0
}
// NewProvisionBroker returns a new ProvisionBroker (with defaults, if applicable)
func NewProvisionBroker() *ProvisionBroker {
return &ProvisionBroker{}
}
// FetchProvisionBroker retrieves provision broker [request] with passed cid.
func (a *API) FetchProvisionBroker(cid CIDType) (*ProvisionBroker, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid provision broker request CID [none]")
}
brokerCID := string(*cid)
matched, err := regexp.MatchString(config.ProvisionBrokerCIDRegex, brokerCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid provision broker request CID [%s]", brokerCID)
}
result, err := a.Get(brokerCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch broker provision request, received JSON: %s", string(result))
}
broker := &ProvisionBroker{}
if err := json.Unmarshal(result, broker); err != nil {
return nil, err
}
return broker, nil
}
// UpdateProvisionBroker updates a broker definition [request].
func (a *API) UpdateProvisionBroker(cid CIDType, cfg *ProvisionBroker) (*ProvisionBroker, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid provision broker request config [nil]")
}
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid provision broker request CID [none]")
}
brokerCID := string(*cid)
matched, err := regexp.MatchString(config.ProvisionBrokerCIDRegex, brokerCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid provision broker request CID [%s]", brokerCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update broker provision request, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(brokerCID, jsonCfg)
if err != nil {
return nil, err
}
broker := &ProvisionBroker{}
if err := json.Unmarshal(result, broker); err != nil {
return nil, err
}
return broker, nil
}
// CreateProvisionBroker creates a new provison broker [request].
func (a *API) CreateProvisionBroker(cfg *ProvisionBroker) (*ProvisionBroker, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid provision broker request config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] create broker provision request, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.ProvisionBrokerPrefix, jsonCfg)
if err != nil {
return nil, err
}
broker := &ProvisionBroker{}
if err := json.Unmarshal(result, broker); err != nil {
return nil, err
}
return broker, nil
}

View File

@ -0,0 +1,234 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Rule Set API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/rule_set
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// RuleSetRule defines a ruleset rule
type RuleSetRule struct {
Criteria string `json:"criteria"` // string
Severity uint `json:"severity"` // uint
Value interface{} `json:"value"` // BUG doc: string, api: actual type returned switches based on Criteria
Wait uint `json:"wait"` // uint
WindowingDuration uint `json:"windowing_duration,omitempty"` // uint
WindowingFunction *string `json:"windowing_function,omitempty"` // string or null
}
// RuleSet defines a ruleset. See https://login.circonus.com/resources/api/calls/rule_set for more information.
type RuleSet struct {
CheckCID string `json:"check"` // string
CID string `json:"_cid,omitempty"` // string
ContactGroups map[uint8][]string `json:"contact_groups"` // [] len 5
Derive *string `json:"derive,omitempty"` // string or null
Link *string `json:"link"` // string or null
MetricName string `json:"metric_name"` // string
MetricTags []string `json:"metric_tags"` // [] len >= 0
MetricType string `json:"metric_type"` // string
Notes *string `json:"notes"` // string or null
Parent *string `json:"parent,omitempty"` // string or null
Rules []RuleSetRule `json:"rules"` // [] len >= 1
Tags []string `json:"tags"` // [] len >= 0
}
// NewRuleSet returns a new RuleSet (with defaults if applicable)
func NewRuleSet() *RuleSet {
return &RuleSet{}
}
// FetchRuleSet retrieves rule set with passed cid.
func (a *API) FetchRuleSet(cid CIDType) (*RuleSet, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid rule set CID [none]")
}
rulesetCID := string(*cid)
matched, err := regexp.MatchString(config.RuleSetCIDRegex, rulesetCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid rule set CID [%s]", rulesetCID)
}
result, err := a.Get(rulesetCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch rule set, received JSON: %s", string(result))
}
ruleset := &RuleSet{}
if err := json.Unmarshal(result, ruleset); err != nil {
return nil, err
}
return ruleset, nil
}
// FetchRuleSets retrieves all rule sets available to API Token.
func (a *API) FetchRuleSets() (*[]RuleSet, error) {
result, err := a.Get(config.RuleSetPrefix)
if err != nil {
return nil, err
}
var rulesets []RuleSet
if err := json.Unmarshal(result, &rulesets); err != nil {
return nil, err
}
return &rulesets, nil
}
// UpdateRuleSet updates passed rule set.
func (a *API) UpdateRuleSet(cfg *RuleSet) (*RuleSet, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid rule set config [nil]")
}
rulesetCID := string(cfg.CID)
matched, err := regexp.MatchString(config.RuleSetCIDRegex, rulesetCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid rule set CID [%s]", rulesetCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update rule set, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(rulesetCID, jsonCfg)
if err != nil {
return nil, err
}
ruleset := &RuleSet{}
if err := json.Unmarshal(result, ruleset); err != nil {
return nil, err
}
return ruleset, nil
}
// CreateRuleSet creates a new rule set.
func (a *API) CreateRuleSet(cfg *RuleSet) (*RuleSet, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid rule set config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] create rule set, sending JSON: %s", string(jsonCfg))
}
resp, err := a.Post(config.RuleSetPrefix, jsonCfg)
if err != nil {
return nil, err
}
ruleset := &RuleSet{}
if err := json.Unmarshal(resp, ruleset); err != nil {
return nil, err
}
return ruleset, nil
}
// DeleteRuleSet deletes passed rule set.
func (a *API) DeleteRuleSet(cfg *RuleSet) (bool, error) {
if cfg == nil {
return false, fmt.Errorf("Invalid rule set config [nil]")
}
return a.DeleteRuleSetByCID(CIDType(&cfg.CID))
}
// DeleteRuleSetByCID deletes rule set with passed cid.
func (a *API) DeleteRuleSetByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, fmt.Errorf("Invalid rule set CID [none]")
}
rulesetCID := string(*cid)
matched, err := regexp.MatchString(config.RuleSetCIDRegex, rulesetCID)
if err != nil {
return false, err
}
if !matched {
return false, fmt.Errorf("Invalid rule set CID [%s]", rulesetCID)
}
_, err = a.Delete(rulesetCID)
if err != nil {
return false, err
}
return true, nil
}
// SearchRuleSets returns rule sets matching the specified search
// query and/or filter. If nil is passed for both parameters all
// rule sets will be returned.
func (a *API) SearchRuleSets(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]RuleSet, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchRuleSets()
}
reqURL := url.URL{
Path: config.RuleSetPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var rulesets []RuleSet
if err := json.Unmarshal(result, &rulesets); err != nil {
return nil, err
}
return &rulesets, nil
}

View File

@ -0,0 +1,231 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// RuleSetGroup API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/rule_set_group
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// RuleSetGroupFormula defines a formula for raising alerts
type RuleSetGroupFormula struct {
Expression interface{} `json:"expression"` // string or uint BUG doc: string, api: string or numeric
RaiseSeverity uint `json:"raise_severity"` // uint
Wait uint `json:"wait"` // uint
}
// RuleSetGroupCondition defines conditions for raising alerts
type RuleSetGroupCondition struct {
MatchingSeverities []string `json:"matching_serverities"` // [] len >= 1
RuleSetCID string `json:"rule_set"` // string
}
// RuleSetGroup defines a ruleset group. See https://login.circonus.com/resources/api/calls/rule_set_group for more information.
type RuleSetGroup struct {
CID string `json:"_cid,omitempty"` // string
ContactGroups map[uint8][]string `json:"contact_groups"` // [] len == 5
Formulas []RuleSetGroupFormula `json:"formulas"` // [] len >= 0
Name string `json:"name"` // string
RuleSetConditions []RuleSetGroupCondition `json:"rule_set_conditions"` // [] len >= 1
Tags []string `json:"tags"` // [] len >= 0
}
// NewRuleSetGroup returns a new RuleSetGroup (with defaults, if applicable)
func NewRuleSetGroup() *RuleSetGroup {
return &RuleSetGroup{}
}
// FetchRuleSetGroup retrieves rule set group with passed cid.
func (a *API) FetchRuleSetGroup(cid CIDType) (*RuleSetGroup, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid rule set group CID [none]")
}
groupCID := string(*cid)
matched, err := regexp.MatchString(config.RuleSetGroupCIDRegex, groupCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid rule set group CID [%s]", groupCID)
}
result, err := a.Get(groupCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch rule set group, received JSON: %s", string(result))
}
rulesetGroup := &RuleSetGroup{}
if err := json.Unmarshal(result, rulesetGroup); err != nil {
return nil, err
}
return rulesetGroup, nil
}
// FetchRuleSetGroups retrieves all rule set groups available to API Token.
func (a *API) FetchRuleSetGroups() (*[]RuleSetGroup, error) {
result, err := a.Get(config.RuleSetGroupPrefix)
if err != nil {
return nil, err
}
var rulesetGroups []RuleSetGroup
if err := json.Unmarshal(result, &rulesetGroups); err != nil {
return nil, err
}
return &rulesetGroups, nil
}
// UpdateRuleSetGroup updates passed rule set group.
func (a *API) UpdateRuleSetGroup(cfg *RuleSetGroup) (*RuleSetGroup, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid rule set group config [nil]")
}
groupCID := string(cfg.CID)
matched, err := regexp.MatchString(config.RuleSetGroupCIDRegex, groupCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid rule set group CID [%s]", groupCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update rule set group, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(groupCID, jsonCfg)
if err != nil {
return nil, err
}
groups := &RuleSetGroup{}
if err := json.Unmarshal(result, groups); err != nil {
return nil, err
}
return groups, nil
}
// CreateRuleSetGroup creates a new rule set group.
func (a *API) CreateRuleSetGroup(cfg *RuleSetGroup) (*RuleSetGroup, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid rule set group config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] create rule set group, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.RuleSetGroupPrefix, jsonCfg)
if err != nil {
return nil, err
}
group := &RuleSetGroup{}
if err := json.Unmarshal(result, group); err != nil {
return nil, err
}
return group, nil
}
// DeleteRuleSetGroup deletes passed rule set group.
func (a *API) DeleteRuleSetGroup(cfg *RuleSetGroup) (bool, error) {
if cfg == nil {
return false, fmt.Errorf("Invalid rule set group config [nil]")
}
return a.DeleteRuleSetGroupByCID(CIDType(&cfg.CID))
}
// DeleteRuleSetGroupByCID deletes rule set group wiht passed cid.
func (a *API) DeleteRuleSetGroupByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, fmt.Errorf("Invalid rule set group CID [none]")
}
groupCID := string(*cid)
matched, err := regexp.MatchString(config.RuleSetGroupCIDRegex, groupCID)
if err != nil {
return false, err
}
if !matched {
return false, fmt.Errorf("Invalid rule set group CID [%s]", groupCID)
}
_, err = a.Delete(groupCID)
if err != nil {
return false, err
}
return true, nil
}
// SearchRuleSetGroups returns rule set groups matching the
// specified search query and/or filter. If nil is passed for
// both parameters all rule set groups will be returned.
func (a *API) SearchRuleSetGroups(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]RuleSetGroup, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchRuleSetGroups()
}
reqURL := url.URL{
Path: config.RuleSetGroupPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var groups []RuleSetGroup
if err := json.Unmarshal(result, &groups); err != nil {
return nil, err
}
return &groups, nil
}

View File

@ -0,0 +1,159 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// User API support - Fetch, Update, and Search
// See: https://login.circonus.com/resources/api/calls/user
// Note: Create and Delete are not supported directly via the User API
// endpoint. See the Account endpoint for inviting and removing users
// from specific accounts.
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// UserContactInfo defines known contact details
type UserContactInfo struct {
SMS string `json:"sms,omitempty"` // string
XMPP string `json:"xmpp,omitempty"` // string
}
// User defines a user. See https://login.circonus.com/resources/api/calls/user for more information.
type User struct {
CID string `json:"_cid,omitempty"` // string
ContactInfo UserContactInfo `json:"contact_info,omitempty"` // UserContactInfo
Email string `json:"email"` // string
Firstname string `json:"firstname"` // string
Lastname string `json:"lastname"` // string
}
// FetchUser retrieves user with passed cid. Pass nil for '/user/current'.
func (a *API) FetchUser(cid CIDType) (*User, error) {
var userCID string
if cid == nil || *cid == "" {
userCID = config.UserPrefix + "/current"
} else {
userCID = string(*cid)
}
matched, err := regexp.MatchString(config.UserCIDRegex, userCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid user CID [%s]", userCID)
}
result, err := a.Get(userCID)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch user, received JSON: %s", string(result))
}
user := new(User)
if err := json.Unmarshal(result, user); err != nil {
return nil, err
}
return user, nil
}
// FetchUsers retrieves all users available to API Token.
func (a *API) FetchUsers() (*[]User, error) {
result, err := a.Get(config.UserPrefix)
if err != nil {
return nil, err
}
var users []User
if err := json.Unmarshal(result, &users); err != nil {
return nil, err
}
return &users, nil
}
// UpdateUser updates passed user.
func (a *API) UpdateUser(cfg *User) (*User, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid user config [nil]")
}
userCID := string(cfg.CID)
matched, err := regexp.MatchString(config.UserCIDRegex, userCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid user CID [%s]", userCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update user, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(userCID, jsonCfg)
if err != nil {
return nil, err
}
user := &User{}
if err := json.Unmarshal(result, user); err != nil {
return nil, err
}
return user, nil
}
// SearchUsers returns users matching a filter (search queries
// are not suppoted by the user endpoint). Pass nil as filter for all
// users available to the API Token.
func (a *API) SearchUsers(filterCriteria *SearchFilterType) (*[]User, error) {
q := url.Values{}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchUsers()
}
reqURL := url.URL{
Path: config.UserPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var users []User
if err := json.Unmarshal(result, &users); err != nil {
return nil, err
}
return &users, nil
}

View File

@ -0,0 +1,232 @@
// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Worksheet API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/worksheet
package api
import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"github.com/circonus-labs/circonus-gometrics/api/config"
)
// WorksheetGraph defines a worksheet cid to be include in the worksheet
type WorksheetGraph struct {
GraphCID string `json:"graph"` // string
}
// WorksheetSmartQuery defines a query to include multiple worksheets
type WorksheetSmartQuery struct {
Name string `json:"name"`
Order []string `json:"order"`
Query string `json:"query"`
}
// Worksheet defines a worksheet. See https://login.circonus.com/resources/api/calls/worksheet for more information.
type Worksheet struct {
CID string `json:"_cid,omitempty"` // string
Description *string `json:"description"` // string or null
Favorite bool `json:"favorite"` // boolean
Graphs []WorksheetGraph `json:"worksheets,omitempty"` // [] len >= 0
Notes *string `json:"notes"` // string or null
SmartQueries []WorksheetSmartQuery `json:"smart_queries,omitempty"` // [] len >= 0
Tags []string `json:"tags"` // [] len >= 0
Title string `json:"title"` // string
}
// NewWorksheet returns a new Worksheet (with defaults, if applicable)
func NewWorksheet() *Worksheet {
return &Worksheet{}
}
// FetchWorksheet retrieves worksheet with passed cid.
func (a *API) FetchWorksheet(cid CIDType) (*Worksheet, error) {
if cid == nil || *cid == "" {
return nil, fmt.Errorf("Invalid worksheet CID [none]")
}
worksheetCID := string(*cid)
matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
}
result, err := a.Get(string(*cid))
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] fetch worksheet, received JSON: %s", string(result))
}
worksheet := new(Worksheet)
if err := json.Unmarshal(result, worksheet); err != nil {
return nil, err
}
return worksheet, nil
}
// FetchWorksheets retrieves all worksheets available to API Token.
func (a *API) FetchWorksheets() (*[]Worksheet, error) {
result, err := a.Get(config.WorksheetPrefix)
if err != nil {
return nil, err
}
var worksheets []Worksheet
if err := json.Unmarshal(result, &worksheets); err != nil {
return nil, err
}
return &worksheets, nil
}
// UpdateWorksheet updates passed worksheet.
func (a *API) UpdateWorksheet(cfg *Worksheet) (*Worksheet, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid worksheet config [nil]")
}
worksheetCID := string(cfg.CID)
matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
if err != nil {
return nil, err
}
if !matched {
return nil, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] update worksheet, sending JSON: %s", string(jsonCfg))
}
result, err := a.Put(worksheetCID, jsonCfg)
if err != nil {
return nil, err
}
worksheet := &Worksheet{}
if err := json.Unmarshal(result, worksheet); err != nil {
return nil, err
}
return worksheet, nil
}
// CreateWorksheet creates a new worksheet.
func (a *API) CreateWorksheet(cfg *Worksheet) (*Worksheet, error) {
if cfg == nil {
return nil, fmt.Errorf("Invalid worksheet config [nil]")
}
jsonCfg, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
if a.Debug {
a.Log.Printf("[DEBUG] create annotation, sending JSON: %s", string(jsonCfg))
}
result, err := a.Post(config.WorksheetPrefix, jsonCfg)
if err != nil {
return nil, err
}
worksheet := &Worksheet{}
if err := json.Unmarshal(result, worksheet); err != nil {
return nil, err
}
return worksheet, nil
}
// DeleteWorksheet deletes passed worksheet.
func (a *API) DeleteWorksheet(cfg *Worksheet) (bool, error) {
if cfg == nil {
return false, fmt.Errorf("Invalid worksheet config [nil]")
}
return a.DeleteWorksheetByCID(CIDType(&cfg.CID))
}
// DeleteWorksheetByCID deletes worksheet with passed cid.
func (a *API) DeleteWorksheetByCID(cid CIDType) (bool, error) {
if cid == nil || *cid == "" {
return false, fmt.Errorf("Invalid worksheet CID [none]")
}
worksheetCID := string(*cid)
matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
if err != nil {
return false, err
}
if !matched {
return false, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
}
_, err = a.Delete(worksheetCID)
if err != nil {
return false, err
}
return true, nil
}
// SearchWorksheets returns worksheets matching the specified search
// query and/or filter. If nil is passed for both parameters all
// worksheets will be returned.
func (a *API) SearchWorksheets(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Worksheet, error) {
q := url.Values{}
if searchCriteria != nil && *searchCriteria != "" {
q.Set("search", string(*searchCriteria))
}
if filterCriteria != nil && len(*filterCriteria) > 0 {
for filter, criteria := range *filterCriteria {
for _, val := range criteria {
q.Add(filter, val)
}
}
}
if q.Encode() == "" {
return a.FetchWorksheets()
}
reqURL := url.URL{
Path: config.WorksheetPrefix,
RawQuery: q.Encode(),
}
result, err := a.Get(reqURL.String())
if err != nil {
return nil, fmt.Errorf("[ERROR] API call error %+v", err)
}
var worksheets []Worksheet
if err := json.Unmarshal(result, &worksheets); err != nil {
return nil, err
}
return &worksheets, nil
}

12
vendor/vendor.json vendored
View File

@ -1171,6 +1171,18 @@
"revision": "7bfb7937d106522a9c6d659864dca47cddcccc8a",
"revisionTime": "2017-01-10T09:44:45Z"
},
{
"checksumSHA1": "PDusd0EuHz0oKiQKwKxFhbETxd8=",
"path": "github.com/circonus-labs/circonus-gometrics/api",
"revision": "dbab9a33438e3f8317407ef5d3a51c29340541db",
"revisionTime": "2017-02-21T20:27:28Z"
},
{
"checksumSHA1": "bQhz/fcyZPmuHSH2qwC4ZtATy5c=",
"path": "github.com/circonus-labs/circonus-gometrics/api/config",
"revision": "dbab9a33438e3f8317407ef5d3a51c29340541db",
"revisionTime": "2017-02-21T20:27:28Z"
},
{
"checksumSHA1": "QhYMdplKQJAMptRaHZBB8CF6HdM=",
"path": "github.com/cloudflare/cloudflare-go",

View File

@ -18,6 +18,7 @@ body.layout-azure,
body.layout-bitbucket,
body.layout-chef,
body.layout-azurerm,
body.layout-circonus,
body.layout-clc,
body.layout-cloudflare,
body.layout-cloudstack,

View File

@ -119,6 +119,11 @@ To make a resource importable, please see the
* azurerm_storage_account
* azurerm_virtual_network
### Circonus
* circonus_check
* circonus_contact_group
### DigitalOcean
* digitalocean_domain

View File

@ -0,0 +1,82 @@
---
layout: "circonus"
page_title: "Circonus: account"
sidebar_current: "docs-circonus-datasource-account"
description: |-
Provides details about a specific Circonus Account.
---
# circonus_account
`circonus_account` provides
[details](https://login.circonus.com/resources/api/calls/account) about a specific
[Circonus Account](https://login.circonus.com/user/docs/Administration/Account).
The `circonus_account` data source can be used for pulling various attributes
about a specific Circonus Account.
## Example Usage
The following example shows how the resource might be used to obtain the metrics
usage and limit of a given Circonus Account.
```
data "circonus_account" "current" {
current = true
}
```
## Argument Reference
The arguments of this data source act as filters for querying the available
regions. The given filters must match exactly one region whose data will be
exported as attributes.
* `id` - (Optional) The Circonus ID of a given account.
* `current` - (Optional) Automatically use the current Circonus Account attached
to the API token making the request.
At least one of the above attributes should be provided when searching for a
account.
## Attributes Reference
The following attributes are exported:
* `address1` - The first line of the address associated with the account.
* `address2` - The second line of the address associated with the account.
* `cc_email` - An optionally specified email address used in the CC line of invoices.
* `id` - The Circonus ID of the selected Account.
* `city` - The city part of the address associated with the account.
* `contact_groups` - A list of IDs for each contact group in the account.
* `country` - The country of the user's address.
* `description` - Description of the account.
* `invites` - An list of users invited to use the platform. Each element in the
list has both an `email` and `role` attribute.
* `name` - The name of the account.
* `owner` - The Circonus ID of the user who owns this account.
* `state_prov` - The state or province of the address associated with the account.
* `timezone` - The timezone that events will be displayed in the web interface
for this account.
* `ui_base_url` - The base URL of this account.
* `usage` - A list of account usage limits. Each element in the list will have
a `limit` attribute, a limit `type`, and a `used` attribute.
* `users` - A list of users who have access to this account. Each element in
the list has both an `id` and a `role`. The `id` is a Circonus ID referencing
the user.

View File

@ -0,0 +1,98 @@
---
layout: "circonus"
page_title: "Circonus: collector"
sidebar_current: "docs-circonus-datasource-collector"
description: |-
Provides details about a specific Circonus Collector.
---
# circonus_collector
`circonus_collector` provides
[details](https://login.circonus.com/resources/api/calls/broker) about a specific
[Circonus Collector](https://login.circonus.com/user/docs/Administration/Brokers).
As well as validating a given Circonus ID, this resource can be used to discover
the additional details about a collector configured within the provider. The
results of a `circonus_collector` API call can return more than one collector
per Circonus ID. Details of each individual collector in the group of
collectors can be found via the `details` attribute described below.
~> **NOTE regarding `cirocnus_collector`:** The `circonus_collector` data source
actually queries and operates on Circonus "brokers" at the broker group level.
The `circonus_collector` is simply a renamed Circonus "broker" to make it clear
what the function of the "broker" actually does: act as a fan-in agent that
either pulls or has metrics pushed into it and funneled back through Circonus.
## Example Usage
The following example shows how the resource might be used to obtain
the name of the Circonus Collector configured on the provider.
```
data "circonus_collector" "ashburn" {
id = "/broker/1"
}
```
## Argument Reference
The arguments of this data source act as filters for querying the available
regions. The given filters must match exactly one region whose data will be
exported as attributes.
* `id` - (Optional) The Circonus ID of a given collector.
At least one of the above attributes should be provided when searching for a
collector.
## Attributes Reference
The following attributes are exported:
* `id` - The Circonus ID of the selected Collector.
* `details` - A list of details about the individual Collector instances that
make up the group of collectors. See below for a list of attributes within
each collector.
* `latitude` - The latitude of the selected Collector.
* `longitude` - The longitude of the selected Collector.
* `name` - The name of the selected Collector.
* `tags` - A list of tags assigned to the selected Collector.
* `type` - The of the selected Collector. This value is either `circonus` for a
Circonus-managed, public Collector, or `enterprise` for a private collector that is
private to an account.
## Collector Details
* `cn` - The CN of an individual Collector in the Collector Group.
* `external_host` - The external host information for an individual Collector in
the Collector Group. This is useful or important when talking with a Collector
through a NAT'ing firewall.
* `external_port` - The external port number for an individual Collector in the
Collector Group. This is useful or important when talking with a Collector through
a NAT'ing firewall.
* `ip` - The IP address of an individual Collector in the Collector Group. This is
the IP address of the interface listening on the network.
* `min_version` - ??
* `modules` - A list of what modules (types of checks) this collector supports.
* `port` - The port the collector responds to the Circonus HTTPS REST wire protocol
on.
* `skew` - The clock drift between this collector and the Circonus server.
* `status` - The status of this particular collector. A string containing either
`active`, `unprovisioned`, `pending`, `provisioned`, or `retired`.
* `version` - The version of the collector software the collector is running.

View File

@ -0,0 +1,28 @@
---
layout: "circonus"
page_title: "Provider: Circonus"
sidebar_current: "docs-circonus-index"
description: |-
A provider for Circonus.
---
# Circonus Provider
The Circonus provider gives the ability to manage a Circonus account.
Use the navigation to the left to read about the available resources.
## Usage
```
provider "circonus" {
key = "b8fec159-f9e5-4fe6-ad2c-dc1ec6751586"
}
```
## Argument Reference
The following arguments are supported:
* `key` - (Required) The Circonus API Key.
* `api_url` - (Optional) The API URL to use to talk with. The default is `https://api.circonus.com/v2`.

View File

@ -0,0 +1,520 @@
---
layout: "circonus"
page_title: "Circonus: circonus_check"
sidebar_current: "docs-circonus-resource-circonus_check"
description: |-
Manages a Circonus check.
---
# circonus\_check
The ``circonus_check`` resource creates and manages a
[Circonus Check](https://login.circonus.com/resources/api/calls/check_bundle).
~> **NOTE regarding `cirocnus_check` vs a Circonus Check Bundle:** The
`circonus_check` resource is implemented in terms of a
[Circonus Check Bundle](https://login.circonus.com/resources/api/calls/check_bundle).
The `circonus_check` creates a higher-level abstraction over the implementation
of a Check Bundle. As such, the naming and structure does not map 1:1 with the
underlying Circonus API.
## Usage
```
variable api_token {
default = "my-token"
}
resource "circonus_check" "usage" {
name = "Circonus Usage Check"
notes = <<-EOF
A check to extract a usage metric.
EOF
collector {
id = "/broker/1"
}
metric {
name = "${circonus_metric.used.name}"
tags = "${circonus_metric.used.tags}"
type = "${circonus_metric.used.type}"
unit = "${circonus_metric.used.unit}"
}
json {
url = "https://api.circonus.com/v2"
http_headers = {
Accept = "application/json"
X-Circonus-App-Name = "TerraformCheck"
X-Circonus-Auth-Token = "${var.api_token}"
}
}
period = 60
tags = ["source:circonus", "author:terraform"]
timeout = 10
}
resource "circonus_metric" "used" {
name = "_usage`0`_used"
type = "numeric"
unit = "qty"
tags = {
source = "circonus"
}
}
```
## Argument Reference
* `active` - (Optional) Whether or not the check is enabled or not (default
`true`).
* `caql` - (Optional) A [Circonus Analytics Query Language
(CAQL)](https://login.circonus.com/user/docs/CAQL) check. See below for
details on how to configure a `caql` check.
* `cloudwatch` - (Optional) A [CloudWatch
check](https://login.circonus.com/user/docs/Data/CheckTypes/CloudWatch) check.
See below for details on how to configure a `cloudwatch` check.
* `collector` - (Required) A collector ID. The collector(s) that are
responsible for running a `circonus_check`. The `id` can be the Circonus ID
for a Circonus collector (a.k.a. "broker") running in the cloud or an
enterprise collector running in your datacenter. One collection of metrics
will be automatically created for each `collector` specified.
* `http` - (Optional) A poll-based HTTP check. See below for details on how to configure
the `http` check.
* `httptrap` - (Optional) An push-based HTTP check. This check method expects
clients to send a specially crafted HTTP JSON payload. See below for details
on how to configure the `httptrap` check.
* `icmp_ping` - (Optional) An ICMP ping check. See below for details on how to
configure the `icmp_ping` check.
* `json` - (Optional) A JSON check. See below for details on how to configure
the `json` check.
* `metric_limit` - (Optional) Setting a metric limit will tell the Circonus
backend to periodically look at the check to see if there are additional
metrics the collector has seen that we should collect. It will not reactivate
metrics previously collected and then marked as inactive. Values are `0` to
disable, `-1` to enable all metrics or `N+` to collect up to the value `N`
(both `-1` and `N+` can not exceed other account restrictions).
* `mysql` - (Optional) A MySQL check. See below for details on how to configure
the `mysql` check.
* `name` - (Optional) The name of the check that will be displayed in the web
interface.
* `notes` - (Optional) Notes about this check.
* `period` - (Optional) The period between each time the check is made in
seconds.
* `postgresql` - (Optional) A PostgreSQL check. See below for details on how to
configure the `postgresql` check.
* `metric` - (Required) A list of one or more `metric` configurations. All
metrics obtained from this check instance will be available as individual
metric streams. See below for a list of supported `metric` attrbutes.
* `tags` - (Optional) A list of tags assigned to this check.
* `target` - (Required) A string containing the location of the thing being
checked. This value changes based on the check type. For example, for an
`http` check type this would be the URL you're checking. For a DNS check it
would be the hostname you wanted to look up.
* `tcp` - (Optional) A TCP check. See below for details on how to configure the
`tcp` check (includes TLS support).
* `timeout` - (Optional) A floating point number representing the maximum number
of seconds this check should wait for a result. Defaults to `10.0`.
## Supported `metric` Attributes
The following attributes are available within a `metric`.
* `active` - (Optional) Whether or not the metric is active or not. Defaults to `true`.
* `name` - (Optional) The name of the metric. A string containing freeform text.
* `tags` - (Optional) A list of tags assigned to the metric.
* `type` - (Required) A string containing either `numeric`, `text`, `histogram`, `composite`, or `caql`.
* `units` - (Optional) The unit of measurement the metric represents (e.g., bytes, seconds, milliseconds). A string containing freeform text.
## Supported Check Types
Circonus supports a variety of different checks. Each check type has its own
set of options that must be configured. Each check type conflicts with every
other check type (i.e. a `circonus_check` configured for a `json` check will
conflict with all other check types, therefore a `postgresql` check must be a
different `circonus_check` resource).
### `caql` Check Type Attributes
* `query` - (Required) The [CAQL
Query](https://login.circonus.com/user/docs/caql_reference) to run.
Available metrics depend on the payload returned in the `caql` check. See the
[`caql` check type](https://login.circonus.com/resources/api/calls/check_bundle) for
additional details.
### `cloudwatch` Check Type Attributes
* `api_key` - (Required) The AWS access key. If this value is not explicitly
set, this value is populated by the environment variable `AWS_ACCESS_KEY_ID`.
* `api_secret` - (Required) The AWS secret key. If this value is not explicitly
set, this value is populated by the environment variable `AWS_SECRET_ACCESS_KEY`.
* `dimmensions` - (Required) A map of the CloudWatch dimmensions to include in
the check.
* `metric` - (Required) A list of metric names to collect in this check.
* `namespace` - (Required) The namespace to pull parameters from.
* `url` - (Required) The AWS URL to pull from. This should be set to the
region-specific endpoint (e.g. prefer
`https://monitoring.us-east-1.amazonaws.com` over
`https://monitoring.amazonaws.com`).
* `version` - (Optional) The version of the Cloudwatch API to use. Defaults to
`2010-08-01`.
Available metrics depend on the payload returned in the `cloudwatch` check. See the
[`cloudwatch` check type](https://login.circonus.com/resources/api/calls/check_bundle) for
additional details. The `circonus_check` `period` attribute must be set to
either `60s` or `300s` for CloudWatch metrics.
Example CloudWatch check (partial metrics collection):
```
variable "cloudwatch_rds_tags" {
type = "list"
default = [
"app:postgresql",
"app:rds",
"source:cloudwatch",
]
}
resource "circonus_check" "rds_metrics" {
active = true
name = "Terraform test: RDS Metrics via CloudWatch"
notes = "Collect RDS metrics"
period = "60s"
collector {
id = "/broker/1"
}
cloudwatch {
dimmensions = {
DBInstanceIdentifier = "my-db-name",
}
metric = [
"CPUUtilization",
"DatabaseConnections",
]
namespace = "AWS/RDS"
url = "https://monitoring.us-east-1.amazonaws.com"
}
metric {
name = "CPUUtilization"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "%"
}
metric {
name = "DatabaseConnections"
tags = [ "${var.cloudwatch_rds_tags}" ]
type = "numeric"
unit = "connections"
}
}
```
### `http` Check Type Attributes
* `auth_method` - (Optional) HTTP Authentication method to use. When set must
be one of the values `Basic`, `Digest`, or `Auto`.
* `auth_password` - (Optional) The password to use during authentication.
* `auth_user` - (Optional) The user to authenticate as.
* `body_regexp` - (Optional) This regular expression is matched against the body
of the response. If a match is not found, the check will be marked as "bad."
* `ca_chain` - (Optional) A path to a file containing all the certificate
authorities that should be loaded to validate the remote certificate (for TLS
checks).
* `certificate_file` - (Optional) A path to a file containing the client
certificate that will be presented to the remote server (for TLS checks).
* `ciphers` - (Optional) A list of ciphers to be used in the TLS protocol (for
HTTPS checks).
* `code` - (Optional) The HTTP code that is expected. If the code received does
not match this regular expression, the check is marked as "bad."
* `extract` - (Optional) This regular expression is matched against the body of
the response globally. The first capturing match is the key and the second
capturing match is the value. Each key/value extracted is registered as a
metric for the check.
* `headers` - (Optional) A map of the HTTP headers to be sent when executing the
check.
* `key_file` - (Optional) A path to a file containing key to be used in
conjunction with the cilent certificate (for TLS checks).
* `method` - (Optional) The HTTP Method to use. Defaults to `GET`.
* `payload` - (Optional) The information transferred as the payload of an HTTP
request.
* `read_limit` - (Optional) Sets an approximate limit on the data read (`0`
means no limit). Default `0`.
* `redirects` - (Optional) The maximum number of HTTP `Location` header
redirects to follow. Default `0`.
* `url` - (Required) The target for this `json` check. The `url` must include
the scheme, host, port (optional), and path to use
(e.g. `https://app1.example.org/healthz`)
* `version` - (Optional) The HTTP version to use. Defaults to `1.1`.
Available metrics include: `body_match`, `bytes`, `cert_end`, `cert_end_in`,
`cert_error`, `cert_issuer`, `cert_start`, `cert_subject`, `code`, `duration`,
`truncated`, `tt_connect`, and `tt_firstbyte`. See the
[`http` check type](https://login.circonus.com/resources/api/calls/check_bundle) for
additional details.
### `httptrap` Check Type Attributes
* `async_metrics` - (Optional) Boolean value specifies whether or not httptrap
metrics are logged immediately or held until the status message is to be
emitted. Default `false`.
* `secret` - (Optional) Specify the secret with which metrics may be
submitted.
Available metrics depend on the payload returned in the `httptrap` doc. See
the [`httptrap` check type](https://login.circonus.com/resources/api/calls/check_bundle)
for additional details.
### `json` Check Type Attributes
* `auth_method` - (Optional) HTTP Authentication method to use. When set must
be one of the values `Basic`, `Digest`, or `Auto`.
* `auth_password` - (Optional) The password to use during authentication.
* `auth_user` - (Optional) The user to authenticate as.
* `ca_chain` - (Optional) A path to a file containing all the certificate
authorities that should be loaded to validate the remote certificate (for TLS
checks).
* `certificate_file` - (Optional) A path to a file containing the client
certificate that will be presented to the remote server (for TLS checks).
* `ciphers` - (Optional) A list of ciphers to be used in the TLS protocol (for
HTTPS checks).
* `headers` - (Optional) A map of the HTTP headers to be sent when executing the
check.
* `key_file` - (Optional) A path to a file containing key to be used in
conjunction with the cilent certificate (for TLS checks).
* `method` - (Optional) The HTTP Method to use. Defaults to `GET`.
* `port` - (Optional) The TCP Port number to use. Defaults to `81`.
* `read_limit` - (Optional) Sets an approximate limit on the data read (`0`
means no limit). Default `0`.
* `redirects` - (Optional) The maximum number of HTTP `Location` header
redirects to follow. Default `0`.
* `url` - (Required) The target for this `json` check. The `url` must include
the scheme, host, port (optional), and path to use
(e.g. `https://app1.example.org/healthz`)
* `version` - (Optional) The HTTP version to use. Defaults to `1.1`.
Available metrics depend on the payload returned in the `json` doc. See the
[`json` check type](https://login.circonus.com/resources/api/calls/check_bundle) for
additional details.
### `icmp_ping` Check Type Attributes
The `icmp_ping` check requires the `target` top-level attribute to be set.
* `availability` - (Optional) The percentage of ping packets that must be
returned for this measurement to be considered successful. Defaults to
`100.0`.
* `count` - (Optional) The number of ICMP ping packets to send. Defaults to
`5`.
* `interval` - (Optional) Interval between packets. Defaults to `2s`.
Available metrics include: `available`, `average`, `count`, `maximum`, and
`minimum`. See the
[`ping_icmp` check type](https://login.circonus.com/resources/api/calls/check_bundle)
for additional details.
### `mysql` Check Type Attributes
The `mysql` check requires the `target` top-level attribute to be set.
* `dsn` - (Required) The [MySQL DSN/connect
string](https://github.com/go-sql-driver/mysql/blob/master/README.md) to
use to talk to MySQL.
* `query` - (Required) The SQL query to execute.
### `postgresql` Check Type Attributes
The `postgresql` check requires the `target` top-level attribute to be set.
* `dsn` - (Required) The [PostgreSQL DSN/connect
string](https://www.postgresql.org/docs/current/static/libpq-connect.html) to
use to talk to PostgreSQL.
* `query` - (Required) The SQL query to execute.
Available metric names are dependent on the output of the `query` being run.
### `tcp` Check Type Attributes
* `banner_regexp` - (Optional) This regular expression is matched against the
response banner. If a match is not found, the check will be marked as bad.
* `ca_chain` - (Optional) A path to a file containing all the certificate
authorities that should be loaded to validate the remote certificate (for TLS
checks).
* `certificate_file` - (Optional) A path to a file containing the client
certificate that will be presented to the remote server (for TLS checks).
* `ciphers` - (Optional) A list of ciphers to be used in the TLS protocol (for
HTTPS checks).
* `host` - (Required) Hostname or IP address of the host to connect to.
* `key_file` - (Optional) A path to a file containing key to be used in
conjunction with the cilent certificate (for TLS checks).
* `port` - (Required) Integer specifying the port on which the management
interface can be reached.
* `tls` - (Optional) When enabled establish a TLS connection.
Available metrics include: `banner`, `banner_match`, `cert_end`, `cert_end_in`,
`cert_error`, `cert_issuer`, `cert_start`, `cert_subject`, `duration`,
`tt_connect`, `tt_firstbyte`. See the
[`tcp` check type](https://login.circonus.com/resources/api/calls/check_bundle)
for additional details.
Sample `tcp` check:
```
resource "circonus_check" "tcp_check" {
name = "TCP and TLS check"
notes = "Obtains the connect time and TTL for the TLS cert"
period = "60s"
collector {
id = "/broker/1"
}
tcp {
host = "127.0.0.1"
port = 443
tls = true
}
metric {
name = "cert_end_in"
tags = [ "${var.tcp_check_tags}" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "tt_connect"
tags = [ "${var.tcp_check_tags}" ]
type = "numeric"
unit = "miliseconds"
}
tags = [ "${var.tcp_check_tags}" ]
}
```
## Out Parameters
* `check_by_collector` - Map of each check (value) that was created for every
specified broker (key).
## Import Example
`circonus_check` supports importing resources. Supposing the following
Terraform (and that the referenced [`circonus_metric`](metric.html) has already
been imported):
```
provider "circonus" {
alias = "b8fec159-f9e5-4fe6-ad2c-dc1ec6751586"
}
resource "circonus_metric" "used" {
name = "_usage`0`_used"
type = "numeric"
}
resource "circonus_check" "usage" {
collector {
id = "/broker/1"
}
json {
url = "https://api.circonus.com/account/current"
http_headers = {
"Accept" = "application/json"
"X-Circonus-App-Name" = "TerraformCheck"
"X-Circonus-Auth-Token" = "${var.api_token}"
}
}
metric {
name = "${circonus_metric.used.name}"
type = "${circonus_metric.used.type}"
}
}
```
It is possible to import a `circonus_check` resource with the following command:
```
$ terraform import circonus_check.usage ID
```
Where `ID` is the `_cid` or Circonus ID of the Check Bundle
(e.g. `/check_bundle/12345`) and `circonus_check.usage` is the name of the
resource whose state will be populated as a result of the command.

View File

@ -0,0 +1,289 @@
---
layout: "circonus"
page_title: "Circonus: circonus_contact_group"
sidebar_current: "docs-circonus-resource-circonus_contact_group"
description: |-
Manages a Circonus Contact Group.
---
# circonus\_contact_group
The ``circonus_contact_group`` resource creates and manages a
[Circonus Contact Group](https://login.circonus.com/user/docs/Alerting/ContactGroups).
## Usage
```
resource "circonus_contact_group" "myteam-alerts" {
name = "MyTeam Alerts"
email {
user = "/user/1234"
}
email {
user = "/user/5678"
}
email {
address = "user@example.com"
}
http {
address = "https://www.example.org/post/endpoint"
format = "json"
method = "POST"
}
irc {
user = "/user/6331"
}
slack {
channel = "#myteam"
team = "T038UT13D"
}
sms {
user = "/user/1234"
}
sms {
address = "8005551212"
}
victorops {
api_key = "xxxx"
critical = 2
info = 5
team = "myteam"
warning = 3
}
xmpp {
user = "/user/9876"
}
aggregation_window = "5m"
alert_option {
severity = 1
reminder = "5m"
escalate_to = "/contact_group/4444"
}
alert_option {
severity = 2
reminder = "15m"
escalate_after = "2h"
escalate_to = "/contact_group/4444"
}
alert_option {
severity = 3
reminder = "24m"
escalate_after = "3d"
escalate_to = "/contact_group/4444"
}
}
```
## Argument Reference
* `aggregation_window` - (Optional) The aggregation window for batching up alert
notifications.
* `alert_option` - (Optional) There is one `alert_option` per severity, where
severity can be any number between 1 (high) and 5 (low). If configured, the
alerting system will remind or escalate alerts to further contact groups if an
alert sent to this contact group is not acknowledged or resolved. See below
for details.
* `email` - (Optional) Zero or more `email` attributes may be present to
dispatch email to Circonus users by referencing their user ID, or by
specifying an email address. See below for details on supported attributes.
* `http` - (Optional) Zero or more `http` attributes may be present to dispatch
[Webhook/HTTP requests](https://login.circonus.com/user/docs/Alerting/ContactGroups#WebhookNotifications)
by Circonus. See below for details on supported attributes.
* `irc` - (Optional) Zero or more `irc` attributes may be present to dispatch
IRC notifications to users. See below for details on supported attributes.
* `long_message` - (Optional) The bulk of the message used in long form alert
messages.
* `long_subject` - (Optional) The subject used in long form alert messages.
* `long_summary` - (Optional) The brief summary used in long form alert messages.
* `name` - (Required) The name of the contact group.
* `pager_duty` - (Optional) Zero or more `pager_duty` attributes may be present
to dispatch to
[Pager Duty teams](https://login.circonus.com/user/docs/Alerting/ContactGroups#PagerDutyOptions).
See below for details on supported attributes.
* `short_message` - (Optional) The subject used in short form alert messages.
* `short_summary` - (Optional) The brief summary used in short form alert
messages.
* `slack` - (Optional) Zero or more `pager_duty` attributes may be present to
dispatch to Pager Duty teams. See below for details on supported attributes.
* `sms` - (Optional) Zero or more `sms` attributes may be present to dispatch
SMS messages to Circonus users by referencing their user ID, or by specifying
an SMS Phone Number. See below for details on supported attributes.
* `tags` - (Optional) A list of tags attached to the Contact Group.
* `victorops` - (Optional) Zero or more `victorops` attributes may be present
to dispatch to
[VictorOps teams](https://login.circonus.com/user/docs/Alerting/ContactGroups#VictorOps).
See below for details on supported attributes.
## Supported Contact Group `alert_option` Attributes
* `escalate_after` - (Optional) How long to wait before escalating an alert that
is received at a given severity.
* `escalate_to` - (Optional) The Contact Group ID who will receive the
escalation.
* `reminder` - (Optional) If specified, reminders will be sent after a user
configurable number of minutes for open alerts.
* `severity` - (Required) An `alert_option` must be assigned to a given severity
level. Valid severity levels range from 1 (highest severity) to 5 (lowest
severity).
## Supported Contact Group `email` Attributes
Either an `address` or `user` attribute is required.
* `address` - (Optional) A well formed email address.
* `user` - (Optional) An email will be sent to the email address of record for
the corresponding user ID (e.g. `/user/1234`).
A `user`'s email address is automatically maintained and kept up to date by the
recipient, whereas an `address` provides no automatic layer of indirection for
keeping the information accurate (including LDAP and SAML-based authentication
mechanisms).
## Supported Contact Group `http` Attributes
* `address` - (Required) URL to send a webhook request to.
* `format` - (Optional) The payload of the request is a JSON-encoded payload
when the `format` is set to `json` (the default). The alternate payload
encoding is `params`.
* `method` - (Optional) The HTTP verb to use when making a request. Either
`GET` or `POST` may be specified. The default verb is `POST`.
## Supported Contact Group `irc` Attributes
* `user` - (Required) When a user has configured IRC on their user account, they
will receive an IRC notification.
## Supported Contact Group `pager_duty` Attributes
* `contact_group_fallback` - (Optional) If there is a problem contacting Pager
Duty, relay the notification automatically to the specified Contact Group
(e.g. `/contact_group/1234`).
* `service_key` - (Required) The Pager Duty Service Key.
* `webook_url` - (Required) The Pager Duty webhook URL that Pager Duty uses to
notify Circonus of acknowledged actions.
## Supported Contact Group `slack` Attributes
* `contact_group_fallback` - (Optional) If there is a problem contacting Slack,
relay the notification automatically to the specified Contact Group
(e.g. `/contact_group/1234`).
* `buttons` - (Optional) Slack notifications can have acknowledgement buttons
built into the notification message itself when enabled. Defaults to `true`.
* `channel` - (Required) Specify what Slack channel Circonus should send alerts
to.
* `team` - (Required) Specify what Slack team Circonus should look in for the
aforementioned `channel`.
* `username` - (Optional) Specify the username Circonus should advertise itself
as in Slack. Defaults to `Circonus`.
## Supported Contact Group `sms` Attributes
Either an `address` or `user` attribute is required.
* `address` - (Optional) SMS Phone Number to send a short notification to.
* `user` - (Optional) An SMS page will be sent to the phone number of record for
the corresponding user ID (e.g. `/user/1234`).
A `user`'s phone number is automatically maintained and kept up to date by the
recipient, whereas an `address` provides no automatic layer of indirection for
keeping the information accurate (including LDAP and SAML-based authentication
mechanisms).
## Supported Contact Group `victorops` Attributes
* `contact_group_fallback` - (Optional) If there is a problem contacting
VictorOps, relay the notification automatically to the specified Contact Group
(e.g. `/contact_group/1234`).
* `api_key` - (Required) The API Key for talking with VictorOps.
* `critical` - (Required)
* `info` - (Required)
* `team` - (Required)
* `warning` - (Required)
## Supported Contact Group `xmpp` Attributes
Either an `address` or `user` attribute is required.
* `address` - (Optional) XMPP address to send a short notification to.
* `user` - (Optional) An XMPP notification will be sent to the XMPP address of
record for the corresponding user ID (e.g. `/user/1234`).
## Import Example
`circonus_contact_group` supports importing resources. Supposing the following
Terraform:
```
provider "circonus" {
alias = "b8fec159-f9e5-4fe6-ad2c-dc1ec6751586"
}
resource "circonus_contact_group" "myteam" {
name = "My Team's Contact Group"
email {
address = "myteam@example.com"
}
slack {
channel = "#myteam"
team = "T024UT03C"
}
}
```
It is possible to import a `circonus_contact_group` resource with the following command:
```
$ terraform import circonus_contact_group.myteam ID
```
Where `ID` is the `_cid` or Circonus ID of the Contact Group
(e.g. `/contact_group/12345`) and `circonus_contact_group.myteam` is the name of
the resource whose state will be populated as a result of the command.

View File

@ -0,0 +1,179 @@
---
layout: "circonus"
page_title: "Circonus: circonus_graph"
sidebar_current: "docs-circonus-resource-circonus_graph"
description: |-
Manages a Circonus graph.
---
# circonus\_graph
The ``circonus_graph`` resource creates and manages a
[Circonus Graph](https://login.circonus.com/user/docs/Visualization/Graph/Create).
https://login.circonus.com/resources/api/calls/graph).
## Usage
```
variable "myapp-tags" {
type = "list"
default = [ "app:myapp", "owner:myteam" ]
}
resource "circonus_graph" "latency-graph" {
name = "Latency Graph"
description = "A sample graph showing off two data points"
notes = "Misc notes about this graph"
graph_style = "line"
line_style = "stepped"
metric {
check = "${circonus_check.api_latency.checks[0]}"
metric_name = "maximum"
metric_type = "numeric"
name = "Maximum Latency"
axis = "left"
color = "#657aa6"
}
metric {
check = "${circonus_check.api_latency.checks[0]}"
metric_name = "minimum"
metric_type = "numeric"
name = "Minimum Latency"
axis = "right"
color = "#0000ff"
}
tags = [ "${var.myapp-tags}" ]
}
```
## Argument Reference
* `description` - (Optional) Description of what the graph is for.
* `graph_style` - (Optional) How the graph should be rendered. Valid options
are `area` or `line` (default).
* `left` - (Optional) A map of graph left axis options. Valid values in `left`
include: `logarithmic` can be set to `0` (default) or `1`; `min` is the `min`
Y axis value on the left; and `max` is the Y axis max value on the left.
* `line_style` - (Optional) How the line should change between points. Can be
either `stepped` (default) or `interpolated`.
* `name` - (Required) The title of the graph.
* `notes` - (Optional) A place for storing notes about this graph.
* `right` - (Optional) A map of graph right axis options. Valid values in
`right` include: `logarithmic` can be set to `0` (default) or `1`; `min` is
the `min` Y axis value on the right; and `max` is the Y axis max value on the
right.
* `metric` - (Optional) A list of metric streams to graph. See below for
options.
* `metric_cluster` - (Optional) A metric cluster to graph. See below for options.
* `tags` - (Optional) A list of tags assigned to this graph.
## `metric` Configuration
An individual metric stream is the underlying source of data points used for
visualization in a graph. Either a `caql` attribute is required or a `check` and
`metric` must be set. The `metric` attribute can have the following options
set.
* `active` - (Optional) A boolean if the metric stream is enabled or not.
* `alpha` - (Optional) A floating point number between 0 and 1.
* `axis` - (Optional) The axis that the metric stream will use. Valid options
are `left` (default) or `right`.
* `caql` - (Optional) A CAQL formula. Conflicts with the `check` and `metric`
attributes.
* `check` - (Optional) The check that this metric stream belongs to.
* `color` - (Optional) A hex-encoded color of the line / area on the graph.
* `formula` - (Optional) Formula that should be aplied to both the values in the
graph and the legend.
* `legend_formula` - (Optional) Formula that should be applied to values in the
legend.
* `function` - (Optional) What derivative value, if any, should be used. Valid
values are: `gauge` (default), `derive`, and `counter (_stddev)`
* `metric_type` - (Required) The type of the metric. Valid values are:
`numeric`, `text`, `histogram`, `composite`, or `caql`.
* `name` - (Optional) A name which will appear in the graph legend.
* `metric_name` - (Optional) The name of the metric stream within the check to
graph.
* `stack` - (Optional) If this metric is to be stacked, which stack set does it
belong to (starting at `0`).
## `metric_cluster` Configuration
A metric cluster selects multiple metric streams together dynamically using a
query language and returns the set of matching metric streams as a single result
set to the graph rendering engine.
* `active` - (Optional) A boolean if the metric cluster is enabled or not.
* `aggregate` - (Optional) The aggregate function to apply across this metric
cluster to create a single value. Valid values are: `none` (default), `min`,
`max`, `sum`, `mean`, or `geometric_mean`.
* `axis` - (Optional) The axis that the metric cluster will use. Valid options
are `left` (default) or `right`.
* `color` - (Optional) A hex-encoded color of the line / area on the graph.
This is a required attribute when `aggregate` is specified.
* `group` - (Optional) The `metric_cluster` that will provide datapoints for this
graph.
* `name` - (Optional) A name which will appear in the graph legend for this
metric cluster.
## Import Example
`circonus_graph` supports importing resources. Supposing the following
Terraform (and that the referenced [`circonus_metric`](metric.html)
and [`circonus_check`](check.html) have already been imported):
```
resource "circonus_graph" "icmp-graph" {
name = "Test graph"
graph_style = "line"
line_style = "stepped"
metric {
check = "${circonus_check.api_latency.checks[0]}"
metric_name = "maximum"
metric_type = "numeric"
name = "Maximum Latency"
axis = "left"
}
}
```
It is possible to import a `circonus_graph` resource with the following command:
```
$ terraform import circonus_graph.usage ID
```
Where `ID` is the `_cid` or Circonus ID of the graph
(e.g. `/graph/bd72aabc-90b9-4039-cc30-c9ab838c18f5`) and
`circonus_graph.icmp-graph` is the name of the resource whose state will be
populated as a result of the command.

View File

@ -0,0 +1,73 @@
---
layout: "circonus"
page_title: "Circonus: circonus_metric"
sidebar_current: "docs-circonus-resource-circonus_metric"
description: |-
Manages a Circonus metric.
---
# circonus\_metric
The ``circonus_metric`` resource creates and manages a
single [metric resource](https://login.circonus.com/resources/api/calls/metric)
that will be instantiated only once a referencing `circonus_check` has been
created.
## Usage
```
resource "circonus_metric" "used" {
name = "_usage`0`_used"
type = "numeric"
units = "qty"
tags = {
author = "terraform"
source = "circonus"
}
}
```
## Argument Reference
* `active` - (Optional) A boolean indicating if the metric is being filtered out
at the `circonus_check`'s collector(s) or not.
* `name` - (Required) The name of the metric. A `name` must be unique within a
`circonus_check` and its meaning is `circonus_check.type` specific.
* `tags` - (Optional) A list of tags assigned to the metric.
* `type` - (Required) The type of metric. This value must be present and can be
one of the following values: `numeric`, `text`, `histogram`, `composite`, or
`caql`.
* `unit` - (Optional) The unit of measurement for this `circonus_metric`.
## Import Example
`circonus_metric` supports importing resources. Supposing the following
Terraform:
```
provider "circonus" {
alias = "b8fec159-f9e5-4fe6-ad2c-dc1ec6751586"
}
resource "circonus_metric" "usage" {
name = "_usage`0`_used"
type = "numeric"
unit = "qty"
tags = { source = "circonus" }
}
```
It is possible to import a `circonus_metric` resource with the following command:
```
$ terraform import circonus_metric.usage ID
```
Where `ID` is a random, never before used UUID and `circonus_metric.usage` is
the name of the resource whose state will be populated as a result of the
command.

View File

@ -0,0 +1,86 @@
---
layout: "circonus"
page_title: "Circonus: circonus_metric_cluster"
sidebar_current: "docs-circonus-resource-circonus_metric_cluster"
description: |-
Manages a Circonus Metric Cluster.
---
# circonus\_metric\_cluster
The ``circonus_metric_cluster`` resource creates and manages a
[Circonus Metric Cluster](https://login.circonus.com/user/docs/Data/View/MetricClusters).
## Usage
```
resource "circonus_metric_cluster" "nomad-job-memory-rss" {
name = "My Job's Resident Memory"
description = <<-EOF
An aggregation of all resident memory metric streams across allocations in a Nomad job.
EOF
query {
definition = "*`nomad-jobname`memory`rss"
type = "average"
}
tags = ["source:nomad","resource:memory"]
}
```
## Argument Reference
* `description` - (Optional) A long-form description of the metric cluster.
* `name` - (Required) The name of the metric cluster. This name must be unique
across all metric clusters in a given Circonus Account.
* `query` - (Required) One or more `query` attributes must be present. Each
`query` must contain both a `definition` and a `type`. See below for details
on supported attributes.
* `tags` - (Optional) A list of tags attached to the metric cluster.
## Supported Metric Cluster `query` Attributes
* `definition` - (Required) The definition of a metric cluster [query](https://login.circonus.com/resources/api/calls/metric_cluster).
* `type` - (Required) The query type to execute per metric cluster. Valid query
types are: `average`, `count`, `counter`, `counter2`, `counter2_stddev`,
`counter_stddev`, `derive`, `derive2`, `derive2_stddev`, `derive_stddev`,
`histogram`, `stddev`, `text`.
## Out parameters
* `id` - ID of the Metric Cluster.
## Import Example
`circonus_metric_cluster` supports importing resources. Supposing the following
Terraform:
```
provider "circonus" {
alias = "b8fec159-f9e5-4fe6-ad2c-dc1ec6751586"
}
resource "circonus_metric_cluster" "mymetriccluster" {
name = "Metric Cluster for a particular metric in a job"
query {
definition = "*`nomad-jobname`memory`rss"
type = "average"
}
}
```
It is possible to import a `circonus_metric_cluster` resource with the following
command:
```
$ terraform import circonus_metric_cluster.mymetriccluster ID
```
Where `ID` is the `_cid` or Circonus ID of the Metric Cluster
(e.g. `/metric_cluster/12345`) and `circonus_metric_cluster.mymetriccluster` is the
name of the resource whose state will be populated as a result of the command.

View File

@ -0,0 +1,377 @@
---
layout: "circonus"
page_title: "Circonus: circonus_rule_set"
sidebar_current: "docs-circonus-resource-circonus_rule_set"
description: |-
Manages a Circonus rule set.
---
# circonus\_rule_set
The ``circonus_rule_set`` resource creates and manages a
[Circonus Rule Set](https://login.circonus.com/resources/api/calls/rule_set).
## Usage
```
variable "myapp-tags" {
type = "list"
default = [ "app:myapp", "owner:myteam" ]
}
resource "circonus_rule_set" "myapp-cert-ttl-alert" {
check = "${circonus_check.myapp-https.checks[0]}"
metric_name = "cert_end_in"
link = "https://wiki.example.org/playbook/how-to-renew-cert"
if {
value {
min_value = "${2 * 24 * 3600}"
}
then {
notify = [ "${circonus_contact_group.myapp-owners.id}" ]
severity = 1
}
}
if {
value {
min_value = "${7 * 24 * 3600}"
}
then {
notify = [ "${circonus_contact_group.myapp-owners.id}" ]
severity = 2
}
}
if {
value {
min_value = "${21 * 24 * 3600}"
}
then {
notify = [ "${circonus_contact_group.myapp-owners.id}" ]
severity = 3
}
}
if {
value {
absent = "24h"
}
then {
notify = [ "${circonus_contact_group.myapp-owners.id}" ]
severity = 1
}
}
tags = [ "${var.myapp-tags}" ]
}
resource "circonus_rule_set" "myapp-healthy-alert" {
check = "${circonus_check.myapp-https.checks[0]}"
metric_name = "duration"
link = "https://wiki.example.org/playbook/debug-down-app"
if {
value {
# SEV1 if it takes more than 9.5s for us to complete an HTTP request
max_value = "${9.5 * 1000}"
}
then {
notify = [ "${circonus_contact_group.myapp-owners.id}" ]
severity = 1
}
}
if {
value {
# SEV2 if it takes more than 5s for us to complete an HTTP request
max_value = "${5 * 1000}"
}
then {
notify = [ "${circonus_contact_group.myapp-owners.id}" ]
severity = 2
}
}
if {
value {
# SEV3 if the average response time is more than 500ms using a moving
# average over the last 10min. Any transient problems should have
# resolved themselves by now. Something's wrong, need to page someone.
over {
last = "10m"
using = "average"
}
max_value = "500"
}
then {
notify = [ "${circonus_contact_group.myapp-owners.id}" ]
severity = 3
}
}
if {
value {
# SEV4 if it takes more than 500ms for us to complete an HTTP request. We
# want to record that things were slow, but not wake anyone up if it
# momentarily pops above 500ms.
min_value = "500"
}
then {
notify = [ "${circonus_contact_group.myapp-owners.id}" ]
severity = 3
}
}
if {
value {
# If for whatever reason we're not recording any values for the last
# 24hrs, fire off a SEV1.
absent = "24h"
}
then {
notify = [ "${circonus_contact_group.myapp-owners.id}" ]
severity = 1
}
}
tags = [ "${var.myapp-tags}" ]
}
resource "circonus_contact_group" "myapp-owners" {
name = "My App Owners"
tags = [ "${var.myapp-tags}" ]
}
resource "circonus_check" "myapp-https" {
name = "My App's HTTPS Check"
notes = <<-EOF
A check to create metric streams for Time to First Byte, HTTP transaction
duration, and the TTL of a TLS cert.
EOF
collector {
id = "/broker/1"
}
http {
code = "^200$"
headers = {
X-Request-Type = "health-check",
}
url = "https://www.example.com/myapp/healthz"
}
metric {
name = "${circonus_metric.myapp-cert-ttl.name}"
tags = "${circonus_metric.myapp-cert-ttl.tags}"
type = "${circonus_metric.myapp-cert-ttl.type}"
unit = "${circonus_metric.myapp-cert-ttl.unit}"
}
metric {
name = "${circonus_metric.myapp-duration.name}"
tags = "${circonus_metric.myapp-duration.tags}"
type = "${circonus_metric.myapp-duration.type}"
unit = "${circonus_metric.myapp-duration.unit}"
}
period = 60
tags = ["source:circonus", "author:terraform"]
timeout = 10
}
resource "circonus_metric" "myapp-cert-ttl" {
name = "cert_end_in"
type = "numeric"
unit = "seconds"
tags = [ "${var.myapp-tags}", "resource:tls" ]
}
resource "circonus_metric" "myapp-duration" {
name = "duration"
type = "numeric"
unit = "miliseconds"
tags = [ "${var.myapp-tags}" ]
}
```
## Argument Reference
* `check` - (Required) The Circonus ID that this Rule Set will use to search for
a metric stream to alert on.
* `if` - (Required) One or more ordered predicate clauses that describe when
Circonus should generate a notification. See below for details on the
structure of an `if` configuration clause.
* `link` - (Optional) A link to external documentation (or anything else you
feel is important) when a notification is sent. This value will show up in
email alerts and the Circonus UI.
* `metric_type` - (Optional) The type of metric this rule set will operate on.
Valid values are `numeric` (the default) and `text`.
* `notes` - (Optional) Notes about this rule set.
* `parent` - (Optional) A Circonus Metric ID that, if specified and active with
a severity 1 alert, will silence this rule set until all of the severity 1
alerts on the parent clear. This value must match the format
`${check_id}_${metric_name}`.
* `metric_name` - (Required) The name of the metric stream within a given check
that this rule set is active on.
* `tags` - (Optional) A list of tags assigned to this rule set.
## `if` Configuration
The `if` configuration block is an
[ordered list of rules](https://login.circonus.com/user/docs/Alerting/Rules/Configure) that
are evaluated in order, first to last. The first `if` condition to evaluate
true shortcircuits all other `if` blocks in this rule set. An `if` block is also
referred to as a "rule." It is advised that all high-severity rules are ordered
before low-severity rules otherwise low-severity rules will mask notifications
that should be delivered with a high-severity.
`if` blocks are made up of two configuration blocks: `value` and `then`. The
`value` configuration block specifies the criteria underwhich the metric streams
are evaluated. The `then` configuration block, optional, specifies what action
to take.
### `value` Configuration
A `value` block can have only one of several "predicate" attributes specified
because they conflict with each other. The list of mutually exclusive
predicates is dependent on the `metric_type`. To evaluate multiple predicates,
create multiple `if` configuration blocks in the proper order.
#### `numeric` Predicates
Metric types of type `numeric` support the following predicates. Only one of
the following predicates may be specified at a time.
* `absent` - (Optional) If a metric has not been observed in this duration the
rule will fire. When present, this duration is evaluated in terms of seconds.
* `changed` - (Optional) A boolean indicating this rule should fire when the
value changes (e.g. `n != n<sub>1</sub>`).
* `min_value` - (Optional) When the value is less than this value, this rule will
fire (e.g. `n < ${min_value}`).
* `max_value` - (Optional) When the value is greater than this value, this rule
will fire (e.g. `n > ${max_value}`).
Additionally, a `numeric` check can also evaluate data based on a windowing
function versus the last measured value in the metric stream. In order to have
a rule evaluate on derived value from a window, include a nested `over`
attribute inside of the `value` configuration block. An `over` attribute needs
two attributes:
* `last` - (Optional) A duration for the sliding window. Default `300s`.
* `using` - (Optional) The window function to use over the `last` interval.
Valid window functions include: `average` (the default), `stddev`, `derive`,
`derive_stddev`, `counter`, `counter_stddev`, `derive_2`, `derive_2_stddev`,
`counter_2`, and `counter_2_stddev`.
#### `text` Predicates
Metric types of type `text` support the following predicates:
* `absent` - (Optional) If a metric has not been observed in this duration the
rule will fire. When present, this duration is evaluated in terms of seconds.
* `changed` - (Optional) A boolean indicating this rule should fire when the
last value in the metric stream changed from it's previous value (e.g. `n !=
n-1`).
* `contains` - (Optional) When the last value in the metric stream the value is
less than this value, this rule will fire (e.g. `strstr(n, ${contains}) !=
NULL`).
* `match` - (Optional) When the last value in the metric stream value exactly
matches this configured value, this rule will fire (e.g. `strcmp(n, ${match})
== 0`).
* `not_contain` - (Optional) When the last value in the metric stream does not
match this configured value, this rule will fire (e.g. `strstr(n, ${contains})
== NULL`).
* `not_match` - (Optional) When the last value in the metric stream does not match
this configured value, this rule will fire (e.g. `strstr(n, ${not_match}) ==
NULL`).
### `then` Configuration
A `then` block can have the following attributes:
* `after` - (Optional) Only execute this notification after waiting for this
number of minutes. Defaults to immediately, or `0m`.
* `notify` - (Optional) A list of contact group IDs to notify when this rule is
sends off a notification.
* `severity` - (Optional) The severity level of the notification. This can be
set to any value between `1` and `5`. Defaults to `1`.
## Import Example
`circonus_rule_set` supports importing resources. Supposing the following
Terraform (and that the referenced [`circonus_metric`](metric.html)
and [`circonus_check`](check.html) have already been imported):
```
resource "circonus_rule_set" "icmp-latency-alert" {
check = "${circonus_check.api_latency.checks[0]}"
metric_name = "maximum"
if {
value {
absent = "600s"
}
then {
notify = [ "${circonus_contact_group.test-trigger.id}" ]
severity = 1
}
}
if {
value {
over {
last = "120s"
using = "average"
}
max_value = 0.5 # units are in miliseconds
}
then {
notify = [ "${circonus_contact_group.test-trigger.id}" ]
severity = 2
}
}
}
```
It is possible to import a `circonus_rule_set` resource with the following command:
```
$ terraform import circonus_rule_set.usage ID
```
Where `ID` is the `_cid` or Circonus ID of the Rule Set
(e.g. `/rule_set/201285_maximum`) and `circonus_rule_set.icmp-latency-alert` is
the name of the resource whose state will be populated as a result of the
command.

View File

@ -0,0 +1,60 @@
<% wrap_layout :inner do %>
<% content_for :sidebar do %>
<div class="docs-sidebar hidden-print affix-top" role="complementary">
<ul class="nav docs-sidenav">
<li<%= sidebar_current("docs-home") %>>
<a href="/docs/providers/index.html">&laquo; Documentation Home</a>
</li>
<li<%= sidebar_current("docs-circonus-index") %>>
<a href="/docs/providers/circonus/index.html">Circonus Provider</a>
</li>
<li<%= sidebar_current(/^docs-circonus-datasource/) %>>
<a href="#">Data Sources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-circonus-datasource-account") %>>
<a href="/docs/providers/circonus/d/account.html">circonus_account</a>
</li>
<li<%= sidebar_current("docs-circonus-datasource-collector") %>>
<a href="/docs/providers/circonus/d/collector.html">circonus_collector</a>
</li>
</ul>
</li>
<li<%= sidebar_current(/^docs-circonus-resource/) %>>
<a href="#">Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-circonus-resource-circonus_check") %>>
<a href="/docs/providers/circonus/r/check.html">circonus_check</a>
</li>
<li<%= sidebar_current("docs-circonus-resource-circonus_contact_group") %>>
<a href="/docs/providers/circonus/r/contact_group.html">circonus_contact_group</a>
</li>
<li<%= sidebar_current("docs-circonus-resource-circonus_graph") %>>
<a href="/docs/providers/circonus/r/graph.html">circonus_graph</a>
</li>
<li<%= sidebar_current("docs-circonus-resource-circonus_metric") %>>
<a href="/docs/providers/circonus/r/metric.html">circonus_metric</a>
</li>
<li<%= sidebar_current("docs-circonus-resource-circonus_metric_cluster") %>>
<a href="/docs/providers/circonus/r/metric_cluster.html">circonus_metric_cluster</a>
</li>
<li<%= sidebar_current("docs-circonus-resource-circonus_rule_set") %>>
<a href="/docs/providers/circonus/r/rule_set.html">circonus_rule_set</a>
</li>
</ul>
</li>
</ul>
</div>
<% end %>
<%= yield %>
<% end %>

View File

@ -218,6 +218,10 @@
<a href="/docs/providers/chef/index.html">Chef</a>
</li>
<li<%= sidebar_current("docs-providers-circonus") %>>
<a href="/docs/providers/circonus/index.html">Circonus</a>
</li>
<li<%= sidebar_current("docs-providers-cloudflare") %>>
<a href="/docs/providers/cloudflare/index.html">CloudFlare</a>
</li>