367 lines
11 KiB
Go
367 lines
11 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.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
|
||
|
}
|