terraform/builtin/providers/circonus/resource_circonus_check_tcp.go

255 lines
7.6 KiB
Go

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
}