github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/circonus/resource_circonus_check_consul.go (about)

     1  package circonus
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"net/url"
     7  	"regexp"
     8  	"strings"
     9  
    10  	"github.com/circonus-labs/circonus-gometrics/api/config"
    11  	"github.com/hashicorp/errwrap"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  )
    14  
    15  const (
    16  	// circonus_check.consul.* resource attribute names
    17  	checkConsulACLTokenAttr             = "acl_token"
    18  	checkConsulAllowStaleAttr           = "allow_stale"
    19  	checkConsulCAChainAttr              = "ca_chain"
    20  	checkConsulCertFileAttr             = "certificate_file"
    21  	checkConsulCheckNameBlacklistAttr   = "check_blacklist"
    22  	checkConsulCiphersAttr              = "ciphers"
    23  	checkConsulDatacenterAttr           = "dc"
    24  	checkConsulHTTPAddrAttr             = "http_addr"
    25  	checkConsulHeadersAttr              = "headers"
    26  	checkConsulKeyFileAttr              = "key_file"
    27  	checkConsulNodeAttr                 = "node"
    28  	checkConsulNodeBlacklistAttr        = "node_blacklist"
    29  	checkConsulServiceAttr              = "service"
    30  	checkConsulServiceNameBlacklistAttr = "service_blacklist"
    31  	checkConsulStateAttr                = "state"
    32  )
    33  
    34  var checkConsulDescriptions = attrDescrs{
    35  	checkConsulACLTokenAttr:             "A Consul ACL token",
    36  	checkConsulAllowStaleAttr:           "Allow Consul to read from a non-leader system",
    37  	checkConsulCAChainAttr:              "A path to a file containing all the certificate authorities that should be loaded to validate the remote certificate (for TLS checks)",
    38  	checkConsulCertFileAttr:             "A path to a file containing the client certificate that will be presented to the remote server (for TLS-enabled checks)",
    39  	checkConsulCheckNameBlacklistAttr:   "A blacklist of check names to exclude from metric results",
    40  	checkConsulCiphersAttr:              "A list of ciphers to be used in the TLS protocol (for HTTPS checks)",
    41  	checkConsulDatacenterAttr:           "The Consul datacenter to extract health information from",
    42  	checkConsulHeadersAttr:              "Map of HTTP Headers to send along with HTTP Requests",
    43  	checkConsulHTTPAddrAttr:             "The HTTP Address of a Consul agent to query",
    44  	checkConsulKeyFileAttr:              "A path to a file containing key to be used in conjunction with the cilent certificate (for TLS checks)",
    45  	checkConsulNodeAttr:                 "Node Name or NodeID of a Consul agent",
    46  	checkConsulNodeBlacklistAttr:        "A blacklist of node names or IDs to exclude from metric results",
    47  	checkConsulServiceAttr:              "Name of the Consul service to check",
    48  	checkConsulServiceNameBlacklistAttr: "A blacklist of service names to exclude from metric results",
    49  	checkConsulStateAttr:                "Check for Consul services in this particular state",
    50  }
    51  
    52  var consulHealthCheckRE = regexp.MustCompile(fmt.Sprintf(`^%s/(%s|%s|%s)/(.+)`, checkConsulV1Prefix, checkConsulV1NodePrefix, checkConsulV1ServicePrefix, checkConsulV1StatePrefix))
    53  
    54  var schemaCheckConsul = &schema.Schema{
    55  	Type:     schema.TypeList,
    56  	Optional: true,
    57  	MaxItems: 1,
    58  	Elem: &schema.Resource{
    59  		Schema: convertToHelperSchema(checkConsulDescriptions, map[schemaAttr]*schema.Schema{
    60  			checkConsulACLTokenAttr: &schema.Schema{
    61  				Type:         schema.TypeString,
    62  				Optional:     true,
    63  				ValidateFunc: validateRegexp(checkConsulACLTokenAttr, `^[a-zA-Z0-9\-]+$`),
    64  			},
    65  			checkConsulAllowStaleAttr: &schema.Schema{
    66  				Type:     schema.TypeBool,
    67  				Optional: true,
    68  				Default:  true,
    69  			},
    70  			checkConsulCAChainAttr: &schema.Schema{
    71  				Type:         schema.TypeString,
    72  				Optional:     true,
    73  				ValidateFunc: validateRegexp(checkConsulCAChainAttr, `.+`),
    74  			},
    75  			checkConsulCertFileAttr: &schema.Schema{
    76  				Type:         schema.TypeString,
    77  				Optional:     true,
    78  				ValidateFunc: validateRegexp(checkConsulCertFileAttr, `.+`),
    79  			},
    80  			checkConsulCheckNameBlacklistAttr: &schema.Schema{
    81  				Type:     schema.TypeList,
    82  				Optional: true,
    83  				Elem: &schema.Schema{
    84  					Type:         schema.TypeString,
    85  					ValidateFunc: validateRegexp(checkConsulCheckNameBlacklistAttr, `^[A-Za-z0-9_-]+$`),
    86  				},
    87  			},
    88  			checkConsulCiphersAttr: &schema.Schema{
    89  				Type:         schema.TypeString,
    90  				Optional:     true,
    91  				ValidateFunc: validateRegexp(checkConsulCiphersAttr, `.+`),
    92  			},
    93  			checkConsulDatacenterAttr: &schema.Schema{
    94  				Type:         schema.TypeString,
    95  				Optional:     true,
    96  				ValidateFunc: validateRegexp(checkConsulCertFileAttr, `^[a-zA-Z0-9]+$`),
    97  			},
    98  			checkConsulHTTPAddrAttr: &schema.Schema{
    99  				Type:         schema.TypeString,
   100  				Optional:     true,
   101  				Default:      defaultCheckConsulHTTPAddr,
   102  				ValidateFunc: validateHTTPURL(checkConsulHTTPAddrAttr, urlIsAbs|urlWithoutPath),
   103  			},
   104  			checkConsulHeadersAttr: &schema.Schema{
   105  				Type:         schema.TypeMap,
   106  				Elem:         schema.TypeString,
   107  				Optional:     true,
   108  				ValidateFunc: validateHTTPHeaders,
   109  			},
   110  			checkConsulKeyFileAttr: &schema.Schema{
   111  				Type:         schema.TypeString,
   112  				Optional:     true,
   113  				ValidateFunc: validateRegexp(checkConsulKeyFileAttr, `.+`),
   114  			},
   115  			checkConsulNodeAttr: &schema.Schema{
   116  				Type:         schema.TypeString,
   117  				Optional:     true,
   118  				ValidateFunc: validateRegexp(checkConsulNodeAttr, `^[a-zA-Z0-9_\-]+$`),
   119  				ConflictsWith: []string{
   120  					checkConsulAttr + "." + checkConsulServiceAttr,
   121  					checkConsulAttr + "." + checkConsulStateAttr,
   122  				},
   123  			},
   124  			checkConsulNodeBlacklistAttr: &schema.Schema{
   125  				Type:     schema.TypeList,
   126  				Optional: true,
   127  				Elem: &schema.Schema{
   128  					Type:         schema.TypeString,
   129  					ValidateFunc: validateRegexp(checkConsulNodeBlacklistAttr, `^[A-Za-z0-9_-]+$`),
   130  				},
   131  			},
   132  			checkConsulServiceAttr: &schema.Schema{
   133  				Type:         schema.TypeString,
   134  				Optional:     true,
   135  				ValidateFunc: validateRegexp(checkConsulServiceAttr, `^[a-zA-Z0-9_\-]+$`),
   136  				ConflictsWith: []string{
   137  					checkConsulAttr + "." + checkConsulNodeAttr,
   138  					checkConsulAttr + "." + checkConsulStateAttr,
   139  				},
   140  			},
   141  			checkConsulServiceNameBlacklistAttr: &schema.Schema{
   142  				Type:     schema.TypeList,
   143  				Optional: true,
   144  				Elem: &schema.Schema{
   145  					Type:         schema.TypeString,
   146  					ValidateFunc: validateRegexp(checkConsulServiceNameBlacklistAttr, `^[A-Za-z0-9_-]+$`),
   147  				},
   148  			},
   149  			checkConsulStateAttr: &schema.Schema{
   150  				Type:         schema.TypeString,
   151  				Optional:     true,
   152  				ValidateFunc: validateRegexp(checkConsulStateAttr, `^(any|passing|warning|critical)$`),
   153  				ConflictsWith: []string{
   154  					checkConsulAttr + "." + checkConsulNodeAttr,
   155  					checkConsulAttr + "." + checkConsulServiceAttr,
   156  				},
   157  			},
   158  		}),
   159  	},
   160  }
   161  
   162  // checkAPIToStateConsul reads the Config data out of circonusCheck.CheckBundle into
   163  // the statefile.
   164  func checkAPIToStateConsul(c *circonusCheck, d *schema.ResourceData) error {
   165  	consulConfig := make(map[string]interface{}, len(c.Config))
   166  
   167  	// swamp is a sanity check: it must be empty by the time this method returns
   168  	swamp := make(map[config.Key]string, len(c.Config))
   169  	for k, s := range c.Config {
   170  		swamp[k] = s
   171  	}
   172  
   173  	saveStringConfigToState := func(apiKey config.Key, attrName schemaAttr) {
   174  		if s, ok := c.Config[apiKey]; ok && s != "" {
   175  			consulConfig[string(attrName)] = s
   176  		}
   177  
   178  		delete(swamp, apiKey)
   179  	}
   180  
   181  	saveStringConfigToState(config.CAChain, checkConsulCAChainAttr)
   182  	saveStringConfigToState(config.CertFile, checkConsulCertFileAttr)
   183  	saveStringConfigToState(config.Ciphers, checkConsulCiphersAttr)
   184  
   185  	// httpAddrURL is used to compose the http_addr value using multiple c.Config
   186  	// values.
   187  	var httpAddrURL url.URL
   188  
   189  	headers := make(map[string]interface{}, len(c.Config)+1) // +1 is for the ACLToken
   190  	headerPrefixLen := len(config.HeaderPrefix)
   191  
   192  	// Explicitly handle several config parameters in sequence: URL, then port,
   193  	// then everything else.
   194  	if v, found := c.Config[config.URL]; found {
   195  		u, err := url.Parse(v)
   196  		if err != nil {
   197  			return errwrap.Wrapf(fmt.Sprintf("unable to parse %q from config: {{err}}", config.URL), err)
   198  		}
   199  
   200  		queryArgs := u.Query()
   201  		if vals, found := queryArgs[apiConsulStaleAttr]; found && len(vals) > 0 {
   202  			consulConfig[string(checkConsulAllowStaleAttr)] = true
   203  		}
   204  
   205  		if dc := queryArgs.Get(apiConsulDatacenterAttr); dc != "" {
   206  			consulConfig[string(checkConsulDatacenterAttr)] = dc
   207  		}
   208  
   209  		httpAddrURL.Host = u.Host
   210  		httpAddrURL.Scheme = u.Scheme
   211  
   212  		md := consulHealthCheckRE.FindStringSubmatch(u.EscapedPath())
   213  		if md == nil {
   214  			return fmt.Errorf("config %q failed to match the health regexp", config.URL)
   215  		}
   216  
   217  		checkMode := md[1]
   218  		checkArg := md[2]
   219  		switch checkMode {
   220  		case checkConsulV1NodePrefix:
   221  			consulConfig[string(checkConsulNodeAttr)] = checkArg
   222  		case checkConsulV1ServicePrefix:
   223  			consulConfig[string(checkConsulServiceAttr)] = checkArg
   224  		case checkConsulV1StatePrefix:
   225  			consulConfig[string(checkConsulStateAttr)] = checkArg
   226  		default:
   227  			return fmt.Errorf("PROVIDER BUG: unsupported check mode %q from %q", checkMode, u.EscapedPath())
   228  		}
   229  
   230  		delete(swamp, config.URL)
   231  	}
   232  
   233  	if v, found := c.Config[config.Port]; found {
   234  		hostInfo := strings.SplitN(httpAddrURL.Host, ":", 2)
   235  		switch {
   236  		case len(hostInfo) == 1 && v != defaultCheckConsulPort, len(hostInfo) > 1:
   237  			httpAddrURL.Host = net.JoinHostPort(hostInfo[0], v)
   238  		}
   239  
   240  		delete(swamp, config.Port)
   241  	}
   242  
   243  	if v, found := c.Config[apiConsulCheckBlacklist]; found {
   244  		consulConfig[checkConsulCheckNameBlacklistAttr] = strings.Split(v, ",")
   245  	}
   246  
   247  	if v, found := c.Config[apiConsulNodeBlacklist]; found {
   248  		consulConfig[checkConsulNodeBlacklistAttr] = strings.Split(v, ",")
   249  	}
   250  
   251  	if v, found := c.Config[apiConsulServiceBlacklist]; found {
   252  		consulConfig[checkConsulServiceNameBlacklistAttr] = strings.Split(v, ",")
   253  	}
   254  
   255  	// NOTE(sean@): headers attribute processed last.  See below.
   256  
   257  	consulConfig[string(checkConsulHTTPAddrAttr)] = httpAddrURL.String()
   258  
   259  	saveStringConfigToState(config.KeyFile, checkConsulKeyFileAttr)
   260  
   261  	// Process the headers last in order to provide an escape hatch capible of
   262  	// overriding any other derived value above.
   263  	for k, v := range c.Config {
   264  		if len(k) <= headerPrefixLen {
   265  			continue
   266  		}
   267  
   268  		// Handle all of the prefix variable headers, like `header_`
   269  		if strings.Compare(string(k[:headerPrefixLen]), string(config.HeaderPrefix)) == 0 {
   270  			key := k[headerPrefixLen:]
   271  			switch key {
   272  			case checkConsulTokenHeader:
   273  				consulConfig[checkConsulACLTokenAttr] = v
   274  			default:
   275  				headers[string(key)] = v
   276  			}
   277  		}
   278  
   279  		delete(swamp, k)
   280  	}
   281  	consulConfig[string(checkConsulHeadersAttr)] = headers
   282  
   283  	whitelistedConfigKeys := map[config.Key]struct{}{
   284  		config.Port:             struct{}{},
   285  		config.ReverseSecretKey: struct{}{},
   286  		config.SubmissionURL:    struct{}{},
   287  		config.URL:              struct{}{},
   288  	}
   289  
   290  	for k := range swamp {
   291  		if _, ok := whitelistedConfigKeys[k]; ok {
   292  			delete(c.Config, k)
   293  		}
   294  
   295  		if _, ok := whitelistedConfigKeys[k]; !ok {
   296  			return fmt.Errorf("PROVIDER BUG: API Config not empty: %#v", swamp)
   297  		}
   298  	}
   299  
   300  	if err := d.Set(checkConsulAttr, []interface{}{consulConfig}); err != nil {
   301  		return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkConsulAttr), err)
   302  	}
   303  
   304  	return nil
   305  }
   306  
   307  func checkConfigToAPIConsul(c *circonusCheck, l interfaceList) error {
   308  	c.Type = string(apiCheckTypeConsul)
   309  
   310  	// Iterate over all `consul` attributes, even though we have a max of 1 in the
   311  	// schema.
   312  	for _, mapRaw := range l {
   313  		consulConfig := newInterfaceMap(mapRaw)
   314  		if v, found := consulConfig[checkConsulCAChainAttr]; found {
   315  			c.Config[config.CAChain] = v.(string)
   316  		}
   317  
   318  		if v, found := consulConfig[checkConsulCertFileAttr]; found {
   319  			c.Config[config.CertFile] = v.(string)
   320  		}
   321  
   322  		if v, found := consulConfig[checkConsulCheckNameBlacklistAttr]; found {
   323  			listRaw := v.([]interface{})
   324  			checks := make([]string, 0, len(listRaw))
   325  			for _, v := range listRaw {
   326  				checks = append(checks, v.(string))
   327  			}
   328  			c.Config[apiConsulCheckBlacklist] = strings.Join(checks, ",")
   329  		}
   330  
   331  		if v, found := consulConfig[checkConsulCiphersAttr]; found {
   332  			c.Config[config.Ciphers] = v.(string)
   333  		}
   334  
   335  		if headers := consulConfig.CollectMap(checkConsulHeadersAttr); headers != nil {
   336  			for k, v := range headers {
   337  				h := config.HeaderPrefix + config.Key(k)
   338  				c.Config[h] = v
   339  			}
   340  		}
   341  
   342  		if v, found := consulConfig[checkConsulKeyFileAttr]; found {
   343  			c.Config[config.KeyFile] = v.(string)
   344  		}
   345  
   346  		{
   347  			// Extract all of the input attributes necessary to construct the
   348  			// Consul agent's URL.
   349  
   350  			httpAddr := consulConfig[checkConsulHTTPAddrAttr].(string)
   351  			checkURL, err := url.Parse(httpAddr)
   352  			if err != nil {
   353  				return errwrap.Wrapf(fmt.Sprintf("Unable to parse %s's attribute %q: {{err}}", checkConsulAttr, httpAddr), err)
   354  			}
   355  
   356  			hostInfo := strings.SplitN(checkURL.Host, ":", 2)
   357  			if len(c.Target) == 0 {
   358  				c.Target = hostInfo[0]
   359  			}
   360  
   361  			if len(hostInfo) > 1 {
   362  				c.Config[config.Port] = hostInfo[1]
   363  			}
   364  
   365  			if v, found := consulConfig[checkConsulNodeAttr]; found && v.(string) != "" {
   366  				checkURL.Path = strings.Join([]string{checkConsulV1Prefix, checkConsulV1NodePrefix, v.(string)}, "/")
   367  			}
   368  
   369  			if v, found := consulConfig[checkConsulServiceAttr]; found && v.(string) != "" {
   370  				checkURL.Path = strings.Join([]string{checkConsulV1Prefix, checkConsulV1ServicePrefix, v.(string)}, "/")
   371  			}
   372  
   373  			if v, found := consulConfig[checkConsulStateAttr]; found && v.(string) != "" {
   374  				checkURL.Path = strings.Join([]string{checkConsulV1Prefix, checkConsulV1StatePrefix, v.(string)}, "/")
   375  			}
   376  
   377  			q := checkURL.Query()
   378  
   379  			if v, found := consulConfig[checkConsulAllowStaleAttr]; found && v.(bool) {
   380  				q.Set(apiConsulStaleAttr, "")
   381  			}
   382  
   383  			if v, found := consulConfig[checkConsulDatacenterAttr]; found && v.(string) != "" {
   384  				q.Set(apiConsulDatacenterAttr, v.(string))
   385  			}
   386  
   387  			checkURL.RawQuery = q.Encode()
   388  
   389  			c.Config[config.URL] = checkURL.String()
   390  		}
   391  
   392  		if v, found := consulConfig[checkConsulNodeBlacklistAttr]; found {
   393  			listRaw := v.([]interface{})
   394  			checks := make([]string, 0, len(listRaw))
   395  			for _, v := range listRaw {
   396  				checks = append(checks, v.(string))
   397  			}
   398  			c.Config[apiConsulNodeBlacklist] = strings.Join(checks, ",")
   399  		}
   400  
   401  		if v, found := consulConfig[checkConsulServiceNameBlacklistAttr]; found {
   402  			listRaw := v.([]interface{})
   403  			checks := make([]string, 0, len(listRaw))
   404  			for _, v := range listRaw {
   405  				checks = append(checks, v.(string))
   406  			}
   407  			c.Config[apiConsulServiceBlacklist] = strings.Join(checks, ",")
   408  		}
   409  	}
   410  
   411  	return nil
   412  }