388 lines
12 KiB
Go
388 lines
12 KiB
Go
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 len(hostInfo) > 1 && c.Config[config.Port] == "" {
|
|
c.Config[config.Port] = hostInfo[1]
|
|
}
|
|
}
|
|
|
|
if v, found := httpConfig[checkHTTPVersionAttr]; found {
|
|
c.Config[config.HTTPVersion] = v.(string)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|