413 lines
14 KiB
Go
413 lines
14 KiB
Go
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
|
|
}
|