Add `consul` check type to `circonus_check` resource (#13030)

* Add the `consul` check type to the `circonus_check` resource.

* Fix a tab-complete fail.

`Parse` != `Path`, but lexically close.

* Dept of 2nd thoughts: `s/service_name/service/g`
This commit is contained in:
Sean Chittenden 2017-03-24 15:05:19 -07:00 committed by GitHub
parent c5935ab1ad
commit 32db4d184f
9 changed files with 886 additions and 4 deletions

View File

@ -25,6 +25,7 @@ const (
const (
apiCheckTypeCAQL circonusCheckType = "caql"
apiCheckTypeConsul circonusCheckType = "consul"
apiCheckTypeICMPPing circonusCheckType = "ping_icmp"
apiCheckTypeHTTP circonusCheckType = "http"
apiCheckTypeJSON circonusCheckType = "json"
@ -108,15 +109,24 @@ func (c *circonusCheck) Fixup() error {
}
func (c *circonusCheck) Validate() error {
if len(c.Metrics) == 0 {
return fmt.Errorf("At least one %s must be specified", checkMetricAttr)
}
if c.Timeout > float32(c.Period) {
return fmt.Errorf("Timeout (%f) can not exceed period (%d)", c.Timeout, c.Period)
}
// Check-type specific validation
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)
}
case apiCheckTypeConsulAttr:
if v, found := c.Config[config.URL]; !found || v == "" {
return fmt.Errorf("%s must have at least one check mode set: %s, %s, or %s must be set", checkConsulAttr, checkConsulServiceAttr, checkConsulNodeAttr, checkConsulStateAttr)
}
}
return nil

View File

@ -17,6 +17,19 @@ const (
providerAutoTagAttr = "auto_tag"
providerKeyAttr = "key"
apiConsulCheckBlacklist = "check_name_blacklist"
apiConsulDatacenterAttr = "dc"
apiConsulNodeBlacklist = "node_blacklist"
apiConsulServiceBlacklist = "service_blacklist"
apiConsulStaleAttr = "stale"
checkConsulTokenHeader = `X-Consul-Token`
checkConsulV1NodePrefix = "node"
checkConsulV1Prefix = "/v1/health"
checkConsulV1ServicePrefix = "service"
checkConsulV1StatePrefix = "state"
defaultCheckConsulHTTPAddr = "http://consul.service.consul"
defaultCheckConsulPort = "8500"
defaultCheckJSONMethod = "GET"
defaultCheckJSONPort = "443"
defaultCheckJSONVersion = "1.1"

View File

@ -33,21 +33,22 @@ const (
checkCAQLAttr = "caql"
checkCloudWatchAttr = "cloudwatch"
checkCollectorAttr = "collector"
checkConsulAttr = "consul"
checkHTTPAttr = "http"
checkHTTPTrapAttr = "httptrap"
checkICMPPingAttr = "icmp_ping"
checkJSONAttr = "json"
checkMetricAttr = "metric"
checkMetricLimitAttr = "metric_limit"
checkMySQLAttr = "mysql"
checkNameAttr = "name"
checkNotesAttr = "notes"
checkPeriodAttr = "period"
checkPostgreSQLAttr = "postgresql"
checkMetricAttr = "metric"
checkStatsdAttr = "statsd"
checkTCPAttr = "tcp"
checkTagsAttr = "tags"
checkTargetAttr = "target"
checkTCPAttr = "tcp"
checkTimeoutAttr = "timeout"
checkTypeAttr = "type"
@ -75,6 +76,7 @@ const (
// Circonus API constants from their API endpoints
apiCheckTypeCAQLAttr apiCheckType = "caql"
apiCheckTypeCloudWatchAttr apiCheckType = "cloudwatch"
apiCheckTypeConsulAttr apiCheckType = "consul"
apiCheckTypeHTTPAttr apiCheckType = "http"
apiCheckTypeHTTPTrapAttr apiCheckType = "httptrap"
apiCheckTypeICMPPingAttr apiCheckType = "ping_icmp"
@ -90,6 +92,7 @@ var checkDescriptions = attrDescrs{
checkCAQLAttr: "CAQL check configuration",
checkCloudWatchAttr: "CloudWatch check configuration",
checkCollectorAttr: "The collector(s) that are responsible for gathering the metrics",
checkConsulAttr: "Consul check configuration",
checkHTTPAttr: "HTTP check configuration",
checkHTTPTrapAttr: "HTTP Trap check configuration",
checkICMPPingAttr: "ICMP ping check configuration",
@ -157,6 +160,7 @@ func resourceCheck() *schema.Resource {
}),
},
},
checkConsulAttr: schemaCheckConsul,
checkHTTPAttr: schemaCheckHTTP,
checkHTTPTrapAttr: schemaCheckHTTPTrap,
checkJSONAttr: schemaCheckJSON,
@ -577,6 +581,7 @@ func checkConfigToAPI(c *circonusCheck, d *schema.ResourceData) error {
checkTypeParseMap := map[string]func(*circonusCheck, interfaceList) error{
checkCAQLAttr: checkConfigToAPICAQL,
checkCloudWatchAttr: checkConfigToAPICloudWatch,
checkConsulAttr: checkConfigToAPIConsul,
checkHTTPAttr: checkConfigToAPIHTTP,
checkHTTPTrapAttr: checkConfigToAPIHTTPTrap,
checkICMPPingAttr: checkConfigToAPIICMPPing,
@ -589,8 +594,17 @@ func checkConfigToAPI(c *circonusCheck, d *schema.ResourceData) error {
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)
switch u := listRaw.(type) {
case []interface{}:
if err := fn(c, u); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to parse type %q: {{err}}", string(checkType)), err)
}
case *schema.Set:
if err := fn(c, u.List()); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to parse type %q: {{err}}", string(checkType)), err)
}
default:
return fmt.Errorf("PROVIDER BUG: unsupported check type interface: %q", checkType)
}
}
}
@ -604,6 +618,7 @@ func parseCheckTypeConfig(c *circonusCheck, d *schema.ResourceData) error {
checkTypeConfigHandlers := map[apiCheckType]func(*circonusCheck, *schema.ResourceData) error{
apiCheckTypeCAQLAttr: checkAPIToStateCAQL,
apiCheckTypeCloudWatchAttr: checkAPIToStateCloudWatch,
apiCheckTypeConsulAttr: checkAPIToStateConsul,
apiCheckTypeHTTPAttr: checkAPIToStateHTTP,
apiCheckTypeHTTPTrapAttr: checkAPIToStateHTTPTrap,
apiCheckTypeICMPPingAttr: checkAPIToStateICMPPing,

View File

@ -0,0 +1,412 @@
package circonus
import (
"fmt"
"net"
"net/url"
"regexp"
"strings"
"github.com/circonus-labs/circonus-gometrics/api/config"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)
const (
// circonus_check.consul.* resource attribute names
checkConsulACLTokenAttr = "acl_token"
checkConsulAllowStaleAttr = "allow_stale"
checkConsulCAChainAttr = "ca_chain"
checkConsulCertFileAttr = "certificate_file"
checkConsulCheckNameBlacklistAttr = "check_blacklist"
checkConsulCiphersAttr = "ciphers"
checkConsulDatacenterAttr = "dc"
checkConsulHTTPAddrAttr = "http_addr"
checkConsulHeadersAttr = "headers"
checkConsulKeyFileAttr = "key_file"
checkConsulNodeAttr = "node"
checkConsulNodeBlacklistAttr = "node_blacklist"
checkConsulServiceAttr = "service"
checkConsulServiceNameBlacklistAttr = "service_blacklist"
checkConsulStateAttr = "state"
)
var checkConsulDescriptions = attrDescrs{
checkConsulACLTokenAttr: "A Consul ACL token",
checkConsulAllowStaleAttr: "Allow Consul to read from a non-leader system",
checkConsulCAChainAttr: "A path to a file containing all the certificate authorities that should be loaded to validate the remote certificate (for TLS checks)",
checkConsulCertFileAttr: "A path to a file containing the client certificate that will be presented to the remote server (for TLS-enabled checks)",
checkConsulCheckNameBlacklistAttr: "A blacklist of check names to exclude from metric results",
checkConsulCiphersAttr: "A list of ciphers to be used in the TLS protocol (for HTTPS checks)",
checkConsulDatacenterAttr: "The Consul datacenter to extract health information from",
checkConsulHeadersAttr: "Map of HTTP Headers to send along with HTTP Requests",
checkConsulHTTPAddrAttr: "The HTTP Address of a Consul agent to query",
checkConsulKeyFileAttr: "A path to a file containing key to be used in conjunction with the cilent certificate (for TLS checks)",
checkConsulNodeAttr: "Node Name or NodeID of a Consul agent",
checkConsulNodeBlacklistAttr: "A blacklist of node names or IDs to exclude from metric results",
checkConsulServiceAttr: "Name of the Consul service to check",
checkConsulServiceNameBlacklistAttr: "A blacklist of service names to exclude from metric results",
checkConsulStateAttr: "Check for Consul services in this particular state",
}
var consulHealthCheckRE = regexp.MustCompile(fmt.Sprintf(`^%s/(%s|%s|%s)/(.+)`, checkConsulV1Prefix, checkConsulV1NodePrefix, checkConsulV1ServicePrefix, checkConsulV1StatePrefix))
var schemaCheckConsul = &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: convertToHelperSchema(checkConsulDescriptions, map[schemaAttr]*schema.Schema{
checkConsulACLTokenAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulACLTokenAttr, `^[a-zA-Z0-9\-]+$`),
},
checkConsulAllowStaleAttr: &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
checkConsulCAChainAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulCAChainAttr, `.+`),
},
checkConsulCertFileAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulCertFileAttr, `.+`),
},
checkConsulCheckNameBlacklistAttr: &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateRegexp(checkConsulCheckNameBlacklistAttr, `^[A-Za-z0-9_-]+$`),
},
},
checkConsulCiphersAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulCiphersAttr, `.+`),
},
checkConsulDatacenterAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulCertFileAttr, `^[a-zA-Z0-9]+$`),
},
checkConsulHTTPAddrAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultCheckConsulHTTPAddr,
ValidateFunc: validateHTTPURL(checkConsulHTTPAddrAttr, urlIsAbs|urlWithoutPath),
},
checkConsulHeadersAttr: &schema.Schema{
Type: schema.TypeMap,
Elem: schema.TypeString,
Optional: true,
ValidateFunc: validateHTTPHeaders,
},
checkConsulKeyFileAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulKeyFileAttr, `.+`),
},
checkConsulNodeAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulNodeAttr, `^[a-zA-Z0-9_\-]+$`),
ConflictsWith: []string{
checkConsulAttr + "." + checkConsulServiceAttr,
checkConsulAttr + "." + checkConsulStateAttr,
},
},
checkConsulNodeBlacklistAttr: &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateRegexp(checkConsulNodeBlacklistAttr, `^[A-Za-z0-9_-]+$`),
},
},
checkConsulServiceAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulServiceAttr, `^[a-zA-Z0-9_\-]+$`),
ConflictsWith: []string{
checkConsulAttr + "." + checkConsulNodeAttr,
checkConsulAttr + "." + checkConsulStateAttr,
},
},
checkConsulServiceNameBlacklistAttr: &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateRegexp(checkConsulServiceNameBlacklistAttr, `^[A-Za-z0-9_-]+$`),
},
},
checkConsulStateAttr: &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateRegexp(checkConsulStateAttr, `^(any|passing|warning|critical)$`),
ConflictsWith: []string{
checkConsulAttr + "." + checkConsulNodeAttr,
checkConsulAttr + "." + checkConsulServiceAttr,
},
},
}),
},
}
// checkAPIToStateConsul reads the Config data out of circonusCheck.CheckBundle into
// the statefile.
func checkAPIToStateConsul(c *circonusCheck, d *schema.ResourceData) error {
consulConfig := 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 != "" {
consulConfig[string(attrName)] = s
}
delete(swamp, apiKey)
}
saveStringConfigToState(config.CAChain, checkConsulCAChainAttr)
saveStringConfigToState(config.CertFile, checkConsulCertFileAttr)
saveStringConfigToState(config.Ciphers, checkConsulCiphersAttr)
// httpAddrURL is used to compose the http_addr value using multiple c.Config
// values.
var httpAddrURL url.URL
headers := make(map[string]interface{}, len(c.Config)+1) // +1 is for the ACLToken
headerPrefixLen := len(config.HeaderPrefix)
// Explicitly handle several config parameters in sequence: URL, then port,
// then everything else.
if v, found := c.Config[config.URL]; found {
u, err := url.Parse(v)
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("unable to parse %q from config: {{err}}", config.URL), err)
}
queryArgs := u.Query()
if vals, found := queryArgs[apiConsulStaleAttr]; found && len(vals) > 0 {
consulConfig[string(checkConsulAllowStaleAttr)] = true
}
if dc := queryArgs.Get(apiConsulDatacenterAttr); dc != "" {
consulConfig[string(checkConsulDatacenterAttr)] = dc
}
httpAddrURL.Host = u.Host
httpAddrURL.Scheme = u.Scheme
md := consulHealthCheckRE.FindStringSubmatch(u.EscapedPath())
if md == nil {
return fmt.Errorf("config %q failed to match the health regexp", config.URL)
}
checkMode := md[1]
checkArg := md[2]
switch checkMode {
case checkConsulV1NodePrefix:
consulConfig[string(checkConsulNodeAttr)] = checkArg
case checkConsulV1ServicePrefix:
consulConfig[string(checkConsulServiceAttr)] = checkArg
case checkConsulV1StatePrefix:
consulConfig[string(checkConsulStateAttr)] = checkArg
default:
return fmt.Errorf("PROVIDER BUG: unsupported check mode %q from %q", checkMode, u.EscapedPath())
}
delete(swamp, config.URL)
}
if v, found := c.Config[config.Port]; found {
hostInfo := strings.SplitN(httpAddrURL.Host, ":", 2)
switch {
case len(hostInfo) == 1 && v != defaultCheckConsulPort, len(hostInfo) > 1:
httpAddrURL.Host = net.JoinHostPort(hostInfo[0], v)
}
delete(swamp, config.Port)
}
if v, found := c.Config[apiConsulCheckBlacklist]; found {
consulConfig[checkConsulCheckNameBlacklistAttr] = strings.Split(v, ",")
}
if v, found := c.Config[apiConsulNodeBlacklist]; found {
consulConfig[checkConsulNodeBlacklistAttr] = strings.Split(v, ",")
}
if v, found := c.Config[apiConsulServiceBlacklist]; found {
consulConfig[checkConsulServiceNameBlacklistAttr] = strings.Split(v, ",")
}
// NOTE(sean@): headers attribute processed last. See below.
consulConfig[string(checkConsulHTTPAddrAttr)] = httpAddrURL.String()
saveStringConfigToState(config.KeyFile, checkConsulKeyFileAttr)
// Process the headers last in order to provide an escape hatch capible of
// overriding any other derived value above.
for k, v := range c.Config {
if len(k) <= headerPrefixLen {
continue
}
// Handle all of the prefix variable headers, like `header_`
if strings.Compare(string(k[:headerPrefixLen]), string(config.HeaderPrefix)) == 0 {
key := k[headerPrefixLen:]
switch key {
case checkConsulTokenHeader:
consulConfig[checkConsulACLTokenAttr] = v
default:
headers[string(key)] = v
}
}
delete(swamp, k)
}
consulConfig[string(checkConsulHeadersAttr)] = headers
whitelistedConfigKeys := map[config.Key]struct{}{
config.Port: struct{}{},
config.ReverseSecretKey: struct{}{},
config.SubmissionURL: struct{}{},
config.URL: 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(checkConsulAttr, []interface{}{consulConfig}); err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkConsulAttr), err)
}
return nil
}
func checkConfigToAPIConsul(c *circonusCheck, l interfaceList) error {
c.Type = string(apiCheckTypeConsul)
// Iterate over all `consul` attributes, even though we have a max of 1 in the
// schema.
for _, mapRaw := range l {
consulConfig := newInterfaceMap(mapRaw)
if v, found := consulConfig[checkConsulCAChainAttr]; found {
c.Config[config.CAChain] = v.(string)
}
if v, found := consulConfig[checkConsulCertFileAttr]; found {
c.Config[config.CertFile] = v.(string)
}
if v, found := consulConfig[checkConsulCheckNameBlacklistAttr]; found {
listRaw := v.([]interface{})
checks := make([]string, 0, len(listRaw))
for _, v := range listRaw {
checks = append(checks, v.(string))
}
c.Config[apiConsulCheckBlacklist] = strings.Join(checks, ",")
}
if v, found := consulConfig[checkConsulCiphersAttr]; found {
c.Config[config.Ciphers] = v.(string)
}
if headers := consulConfig.CollectMap(checkConsulHeadersAttr); headers != nil {
for k, v := range headers {
h := config.HeaderPrefix + config.Key(k)
c.Config[h] = v
}
}
if v, found := consulConfig[checkConsulKeyFileAttr]; found {
c.Config[config.KeyFile] = v.(string)
}
{
// Extract all of the input attributes necessary to construct the
// Consul agent's URL.
httpAddr := consulConfig[checkConsulHTTPAddrAttr].(string)
checkURL, err := url.Parse(httpAddr)
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Unable to parse %s's attribute %q: {{err}}", checkConsulAttr, httpAddr), err)
}
hostInfo := strings.SplitN(checkURL.Host, ":", 2)
if len(c.Target) == 0 {
c.Target = hostInfo[0]
}
if len(hostInfo) > 1 {
c.Config[config.Port] = hostInfo[1]
}
if v, found := consulConfig[checkConsulNodeAttr]; found && v.(string) != "" {
checkURL.Path = strings.Join([]string{checkConsulV1Prefix, checkConsulV1NodePrefix, v.(string)}, "/")
}
if v, found := consulConfig[checkConsulServiceAttr]; found && v.(string) != "" {
checkURL.Path = strings.Join([]string{checkConsulV1Prefix, checkConsulV1ServicePrefix, v.(string)}, "/")
}
if v, found := consulConfig[checkConsulStateAttr]; found && v.(string) != "" {
checkURL.Path = strings.Join([]string{checkConsulV1Prefix, checkConsulV1StatePrefix, v.(string)}, "/")
}
q := checkURL.Query()
if v, found := consulConfig[checkConsulAllowStaleAttr]; found && v.(bool) {
q.Set(apiConsulStaleAttr, "")
}
if v, found := consulConfig[checkConsulDatacenterAttr]; found && v.(string) != "" {
q.Set(apiConsulDatacenterAttr, v.(string))
}
checkURL.RawQuery = q.Encode()
c.Config[config.URL] = checkURL.String()
}
if v, found := consulConfig[checkConsulNodeBlacklistAttr]; found {
listRaw := v.([]interface{})
checks := make([]string, 0, len(listRaw))
for _, v := range listRaw {
checks = append(checks, v.(string))
}
c.Config[apiConsulNodeBlacklist] = strings.Join(checks, ",")
}
if v, found := consulConfig[checkConsulServiceNameBlacklistAttr]; found {
listRaw := v.([]interface{})
checks := make([]string, 0, len(listRaw))
for _, v := range listRaw {
checks = append(checks, v.(string))
}
c.Config[apiConsulServiceBlacklist] = strings.Join(checks, ",")
}
}
return nil
}

View File

@ -0,0 +1,282 @@
package circonus
import (
"fmt"
"regexp"
"testing"
"github.com/circonus-labs/circonus-gometrics/api/config"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccCirconusCheckConsul_node(t *testing.T) {
checkName := fmt.Sprintf("Terraform test: consul.service.consul mode=state check - %s", acctest.RandString(5))
checkNode := fmt.Sprintf("my-node-name-or-node-id-%s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckConsulConfigV1HealthNodeFmt, checkName, checkNode),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.consul_server", "active", "true"),
resource.TestMatchResourceAttr("circonus_check.consul_server", "check_id", regexp.MustCompile(config.CheckCIDRegex)),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.2084916526.id", "/broker/2110"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.#", "1"),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ca_chain", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.certificate_file", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ciphers", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.key_file", ""),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.dc", "dc2"),
resource.TestCheckNoResourceAttr("circonus_check.consul_server", "consul.0.headers"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.http_addr", "http://consul.service.consul:8501"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.node", checkNode),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.node_blacklist.#", "3"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.node_blacklist.0", "a"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.node_blacklist.1", "bad"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.node_blacklist.2", "node"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "notes", ""),
resource.TestCheckResourceAttr("circonus_check.consul_server", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.name", "KnownLeader"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.type", "text"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.name", "LastContact"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "target", "consul.service.consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "type", "consul"),
),
},
},
})
}
func TestAccCirconusCheckConsul_service(t *testing.T) {
checkName := fmt.Sprintf("Terraform test: consul.service.consul mode=service check - %s", acctest.RandString(5))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckConsulConfigV1HealthServiceFmt, checkName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.consul_server", "active", "true"),
resource.TestMatchResourceAttr("circonus_check.consul_server", "check_id", regexp.MustCompile(config.CheckCIDRegex)),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.2084916526.id", "/broker/2110"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.#", "1"),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ca_chain", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.certificate_file", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ciphers", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.key_file", ""),
resource.TestCheckNoResourceAttr("circonus_check.consul_server", "consul.0.headers"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.http_addr", "http://consul.service.consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.service", "consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.service_blacklist.#", "3"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.service_blacklist.0", "bad"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.service_blacklist.1", "hombre"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.service_blacklist.2", "service"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.consul_server", "notes", ""),
resource.TestCheckResourceAttr("circonus_check.consul_server", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.name", "KnownLeader"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.type", "text"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.name", "LastContact"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "target", "consul.service.consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "type", "consul"),
),
},
},
})
}
func TestAccCirconusCheckConsul_state(t *testing.T) {
checkName := fmt.Sprintf("Terraform test: consul.service.consul mode=state check - %s", acctest.RandString(5))
checkState := "critical"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDestroyCirconusCheckBundle,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(testAccCirconusCheckConsulConfigV1HealthStateFmt, checkName, checkState),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("circonus_check.consul_server", "active", "true"),
resource.TestMatchResourceAttr("circonus_check.consul_server", "check_id", regexp.MustCompile(config.CheckCIDRegex)),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.#", "1"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "collector.2084916526.id", "/broker/2110"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.#", "1"),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ca_chain", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.certificate_file", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.ciphers", ""),
// resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.key_file", ""),
resource.TestCheckNoResourceAttr("circonus_check.consul_server", "consul.0.headers"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.http_addr", "http://consul.service.consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.state", checkState),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.check_blacklist.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.check_blacklist.0", "worthless"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "consul.0.check_blacklist.1", "check"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "name", checkName),
resource.TestCheckResourceAttr("circonus_check.consul_server", "notes", ""),
resource.TestCheckResourceAttr("circonus_check.consul_server", "period", "60s"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.name", "KnownLeader"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3333874791.type", "text"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.active", "true"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.name", "LastContact"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.type", "numeric"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "metric.3148913305.unit", "seconds"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.#", "2"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.1401442048", "lifecycle:unittest"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "tags.2058715988", "source:consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "target", "consul.service.consul"),
resource.TestCheckResourceAttr("circonus_check.consul_server", "type", "consul"),
),
},
},
})
}
const testAccCirconusCheckConsulConfigV1HealthNodeFmt = `
resource "circonus_check" "consul_server" {
active = true
name = "%s"
period = "60s"
collector {
id = "/broker/2110"
}
consul {
dc = "dc2"
http_addr = "http://consul.service.consul:8501"
node = "%s"
node_blacklist = ["a","bad","node"]
}
metric {
name = "LastContact"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "KnownLeader"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "text"
}
tags = [ "source:consul", "lifecycle:unittest" ]
target = "consul.service.consul"
}
`
const testAccCirconusCheckConsulConfigV1HealthServiceFmt = `
resource "circonus_check" "consul_server" {
active = true
name = "%s"
period = "60s"
collector {
id = "/broker/2110"
}
consul {
service = "consul"
service_blacklist = ["bad","hombre","service"]
}
metric {
name = "LastContact"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "KnownLeader"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "text"
}
tags = [ "source:consul", "lifecycle:unittest" ]
target = "consul.service.consul"
}
`
const testAccCirconusCheckConsulConfigV1HealthStateFmt = `
resource "circonus_check" "consul_server" {
active = true
name = "%s"
period = "60s"
collector {
id = "/broker/2110"
}
consul {
state = "%s"
check_blacklist = ["worthless","check"]
}
metric {
name = "LastContact"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "KnownLeader"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "text"
}
tags = [ "source:consul", "lifecycle:unittest" ]
target = "consul.service.consul"
}
`

View File

@ -372,6 +372,10 @@ func checkConfigToAPIHTTP(c *circonusCheck, l interfaceList) error {
if len(c.Target) == 0 {
c.Target = hostInfo[0]
}
if len(hostInfo) > 1 && c.Config[config.Port] == "" {
c.Config[config.Port] = hostInfo[1]
}
}
if v, found := httpConfig[checkHTTPVersionAttr]; found {

View File

@ -355,6 +355,10 @@ func checkConfigToAPIJSON(c *circonusCheck, l interfaceList) error {
if len(c.Target) == 0 {
c.Target = hostInfo[0]
}
if len(hostInfo) > 1 && c.Config[config.Port] == "" {
c.Config[config.Port] = hostInfo[1]
}
}
if v, found := jsonConfig[checkJSONVersionAttr]; found {

View File

@ -314,6 +314,7 @@ type urlParseFlags int
const (
urlIsAbs urlParseFlags = 1 << iota
urlOptional
urlWithoutPath
urlWithoutPort
urlWithoutSchema
)
@ -345,6 +346,10 @@ func validateHTTPURL(attrName schemaAttr, checkFlags urlParseFlags) func(v inter
errors = append(errors, fmt.Errorf("Schema is present on URL %q (HINT: drop the https://%s)", v.(string), v.(string)))
}
if checkFlags&urlWithoutPath != 0 && u.Path != "" {
errors = append(errors, fmt.Errorf("Path is present on URL %q (HINT: drop the %s)", v.(string), u.Path))
}
if checkFlags&urlWithoutPort != 0 {
hostParts := strings.SplitN(u.Host, ":", 2)
if len(hostParts) != 1 {

View File

@ -88,6 +88,9 @@ resource "circonus_metric" "used" {
enterprise collector running in your datacenter. One collection of metrics
will be automatically created for each `collector` specified.
* `consul` - (Optional) A native Consul check. See below for details on how to
configure a `consul` check.
* `http` - (Optional) A poll-based HTTP check. See below for details on how to configure
the `http` check.
@ -249,6 +252,140 @@ resource "circonus_check" "rds_metrics" {
}
```
### `consul` Check Type Attributes
* `acl_token` - (Optional) An ACL Token authenticate the API request. When an
ACL Token is set, this value is transmitted as an HTTP Header in order to not
show up in any logs. The default value is an empty string.
* `allow_stale` - (Optional) A boolean value that indicates whether or not this
check should require the health information come from the Consul leader node.
For scalability reasons, this value defaults to `false`. See below for
details on detecting the staleness of health information.
* `ca_chain` - (Optional) A path to a file containing all the certificate
authorities that should be loaded to validate the remote certificate (required
when `http_addr` is a TLS-enabled endpoint).
* `certificate_file` - (Optional) A path to a file containing the client
certificate that will be presented to the remote server (required when
`http_addr` is a TLS-enabled endpoint).
* `check_blacklist` - (Optional) A list of check names to exclude from the
result of checks (i.e. no metrics will be generated by whose check name is in
the `check_blacklist`). This blacklist is applied to the `node`,
`service`, and `state` check modes.
* `ciphers` - (Optional) A list of ciphers to be used in the TLS protocol
(only used when `http_addr` is a TLS-enabled endpoint).
* `dc` - (Optional) Explicitly name the Consul datacenter to use. The default
value is an empty string. When an empty value is specified, the Consul
datacenter of the agent at the `http_addr` is implicitly used.
* `headers` - (Optional) A map of the HTTP headers to be sent when executing the
check. NOTE: the `headers` attribute is processed last and will takes
precidence over any other derived value that is transmitted as an HTTP header
to Consul (i.e. it is possible to override the `acl_token` by setting a
headers value).
* `http_addr` - (Optional) The Consul HTTP endpoint to to query for health
information. The default value is `http://consul.service.consul:8500`. The
scheme must change from `http` to `https` when the endpoint has been
TLS-enabled.
* `key_file` - (Optional) A path to a file containing key to be used in
conjunction with the cilent certificate (required when `http_addr` is a
TLS-enabled endpoint).
* `node` - (Optional) Check the health of this node. The value can be either a
Consul Node ID (Consul Version >= 0.7.4) or Node Name. See also the
`service_blacklist`, `node_blacklist`, and `check_blacklist` attributes. This
attribute conflicts with the `service` and `state` attributes.
* `node_blacklist` - (Optional) A list of node IDs or node names to exclude from
the results of checks (i.e. no metrics will be generated from nodes in the
`node_blacklist`). This blacklist is applied to the `node`, `service`, and
`state` check modes.
* `service` - (Optional) Check the cluster-wide health of this named service.
See also the `service_blacklist`, `node_blacklist`, and `check_blacklist`
attributes. This attribute conflicts with the `node` and `state` attributes.
* `service_blacklist` - (Optional) A list of service names to exclude from the
result of checks (i.e. no metrics will be generated by services whose service
name is in the `service_blacklist`). This blacklist is applied to the `node`,
`service`, and `state` check modes.
* `state` - (Optional) A Circonus check to monitor Consul checks across the
entire Consul cluster. This value may be either `passing`, `warning`, or
`critical`. This `consul` check mode is intended to act as the cluster check
of last resort. This check type is useful when first starting and is intended
to act as a check of last resort before transitioning to explicitly defined
checks for individual services or nodes. The metrics returned from check will
be sorted based on the `CreateIndex` of the entry in order to have a stable
set of metrics in the array of returned values. See also the
`service_blacklist`, `node_blacklist`, and `check_blacklist` attributes. This
attribute conflicts with the `node` and `state` attributes.
Available metrics depend on the consul check being performed (`node`, `service`,
or `state`). In addition to the data avilable from the endpoints, the `consul`
check also returns a set of metrics that are a variant of:
`{Num,Pct}{,Passing,Warning,Critical}{Checks,Nodes,Services}` (see the
`GLOB_BRACE` section of your local `glob(3)` documentation).
Example Consul check (partial metrics collection):
```
resource "circonus_check" "consul_server" {
active = true
name = "%s"
period = "60s"
collector {
# Collector ID must be an Enterprise broker able to reach the Consul agent
# listed in `http_addr`.
id = "/broker/2110"
}
consul {
service = "consul"
# Other consul check modes:
# node = "consul1"
# state = "critical"
}
metric {
name = "NumNodes"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "numeric"
}
metric {
name = "LastContact"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "numeric"
unit = "seconds"
}
metric {
name = "Index"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "numeric"
unit = "transactions"
}
metric {
name = "KnownLeader"
tags = [ "source:consul", "lifecycle:unittest" ]
type = "text"
}
tags = [ "source:consul", "lifecycle:unittest" ]
}
```
### `http` Check Type Attributes
* `auth_method` - (Optional) HTTP Authentication method to use. When set must