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

     1  package circonus
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"net/url"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/circonus-labs/circonus-gometrics/api/config"
    13  	"github.com/hashicorp/errwrap"
    14  	"github.com/hashicorp/terraform/helper/hashcode"
    15  	"github.com/hashicorp/terraform/helper/schema"
    16  )
    17  
    18  const (
    19  	// circonus_check.http.* resource attribute names
    20  	checkHTTPAuthMethodAttr   = "auth_method"
    21  	checkHTTPAuthPasswordAttr = "auth_password"
    22  	checkHTTPAuthUserAttr     = "auth_user"
    23  	checkHTTPBodyRegexpAttr   = "body_regexp"
    24  	checkHTTPCAChainAttr      = "ca_chain"
    25  	checkHTTPCertFileAttr     = "certificate_file"
    26  	checkHTTPCiphersAttr      = "ciphers"
    27  	checkHTTPCodeRegexpAttr   = "code"
    28  	checkHTTPExtractAttr      = "extract"
    29  	checkHTTPHeadersAttr      = "headers"
    30  	checkHTTPKeyFileAttr      = "key_file"
    31  	checkHTTPMethodAttr       = "method"
    32  	checkHTTPPayloadAttr      = "payload"
    33  	checkHTTPReadLimitAttr    = "read_limit"
    34  	checkHTTPURLAttr          = "url"
    35  	checkHTTPVersionAttr      = "version"
    36  )
    37  
    38  var checkHTTPDescriptions = attrDescrs{
    39  	checkHTTPAuthMethodAttr:   "The HTTP Authentication method",
    40  	checkHTTPAuthPasswordAttr: "The HTTP Authentication user password",
    41  	checkHTTPAuthUserAttr:     "The HTTP Authentication user name",
    42  	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.`,
    43  	checkHTTPCAChainAttr:      "A path to a file containing all the certificate authorities that should be loaded to validate the remote certificate (for TLS checks)",
    44  	checkHTTPCodeRegexpAttr:   `The HTTP code that is expected. If the code received does not match this regular expression, the check is marked as "bad."`,
    45  	checkHTTPCiphersAttr:      "A list of ciphers to be used in the TLS protocol (for HTTPS checks)",
    46  	checkHTTPCertFileAttr:     "A path to a file containing the client certificate that will be presented to the remote server (for TLS-enabled checks)",
    47  	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.",
    48  	checkHTTPHeadersAttr:      "Map of HTTP Headers to send along with HTTP Requests",
    49  	checkHTTPKeyFileAttr:      "A path to a file containing key to be used in conjunction with the cilent certificate (for TLS checks)",
    50  	checkHTTPMethodAttr:       "The HTTP method to use",
    51  	checkHTTPPayloadAttr:      "The information transferred as the payload of an HTTP request",
    52  	checkHTTPReadLimitAttr:    "Sets an approximate limit on the data read (0 means no limit)",
    53  	checkHTTPURLAttr:          "The URL to use as the target of the check",
    54  	checkHTTPVersionAttr:      "Sets the HTTP version for the check to use",
    55  }
    56  
    57  var schemaCheckHTTP = &schema.Schema{
    58  	Type:     schema.TypeSet,
    59  	Optional: true,
    60  	MaxItems: 1,
    61  	MinItems: 1,
    62  	Set:      hashCheckHTTP,
    63  	Elem: &schema.Resource{
    64  		Schema: convertToHelperSchema(checkHTTPDescriptions, map[schemaAttr]*schema.Schema{
    65  			checkHTTPAuthMethodAttr: &schema.Schema{
    66  				Type:         schema.TypeString,
    67  				Optional:     true,
    68  				ValidateFunc: validateRegexp(checkHTTPAuthMethodAttr, `^(?:Basic|Digest|Auto)$`),
    69  			},
    70  			checkHTTPAuthPasswordAttr: &schema.Schema{
    71  				Type:         schema.TypeString,
    72  				Optional:     true,
    73  				Sensitive:    true,
    74  				ValidateFunc: validateRegexp(checkHTTPAuthPasswordAttr, `^.*`),
    75  			},
    76  			checkHTTPAuthUserAttr: &schema.Schema{
    77  				Type:         schema.TypeString,
    78  				Optional:     true,
    79  				ValidateFunc: validateRegexp(checkHTTPAuthUserAttr, `[^:]+`),
    80  			},
    81  			checkHTTPBodyRegexpAttr: &schema.Schema{
    82  				Type:         schema.TypeString,
    83  				Optional:     true,
    84  				ValidateFunc: validateRegexp(checkHTTPBodyRegexpAttr, `.+`),
    85  			},
    86  			checkHTTPCAChainAttr: &schema.Schema{
    87  				Type:         schema.TypeString,
    88  				Optional:     true,
    89  				ValidateFunc: validateRegexp(checkHTTPCAChainAttr, `.+`),
    90  			},
    91  			checkHTTPCertFileAttr: &schema.Schema{
    92  				Type:         schema.TypeString,
    93  				Optional:     true,
    94  				ValidateFunc: validateRegexp(checkHTTPCertFileAttr, `.+`),
    95  			},
    96  			checkHTTPCiphersAttr: &schema.Schema{
    97  				Type:         schema.TypeString,
    98  				Optional:     true,
    99  				ValidateFunc: validateRegexp(checkHTTPCiphersAttr, `.+`),
   100  			},
   101  			checkHTTPCodeRegexpAttr: &schema.Schema{
   102  				Type:         schema.TypeString,
   103  				Optional:     true,
   104  				Default:      defaultCheckHTTPCodeRegexp,
   105  				ValidateFunc: validateRegexp(checkHTTPCodeRegexpAttr, `.+`),
   106  			},
   107  			checkHTTPExtractAttr: &schema.Schema{
   108  				Type:         schema.TypeString,
   109  				Optional:     true,
   110  				ValidateFunc: validateRegexp(checkHTTPExtractAttr, `.+`),
   111  			},
   112  			checkHTTPHeadersAttr: &schema.Schema{
   113  				Type:         schema.TypeMap,
   114  				Elem:         schema.TypeString,
   115  				Optional:     true,
   116  				ValidateFunc: validateHTTPHeaders,
   117  			},
   118  			checkHTTPKeyFileAttr: &schema.Schema{
   119  				Type:         schema.TypeString,
   120  				Optional:     true,
   121  				ValidateFunc: validateRegexp(checkHTTPKeyFileAttr, `.+`),
   122  			},
   123  			checkHTTPMethodAttr: &schema.Schema{
   124  				Type:         schema.TypeString,
   125  				Optional:     true,
   126  				Default:      defaultCheckHTTPMethod,
   127  				ValidateFunc: validateRegexp(checkHTTPMethodAttr, `\S+`),
   128  			},
   129  			checkHTTPPayloadAttr: &schema.Schema{
   130  				Type:         schema.TypeString,
   131  				Optional:     true,
   132  				ValidateFunc: validateRegexp(checkHTTPPayloadAttr, `\S+`),
   133  			},
   134  			checkHTTPReadLimitAttr: &schema.Schema{
   135  				Type:     schema.TypeInt,
   136  				Optional: true,
   137  				ValidateFunc: validateFuncs(
   138  					validateIntMin(checkHTTPReadLimitAttr, 0),
   139  				),
   140  			},
   141  			checkHTTPURLAttr: &schema.Schema{
   142  				Type:     schema.TypeString,
   143  				Required: true,
   144  				ValidateFunc: validateFuncs(
   145  					validateHTTPURL(checkHTTPURLAttr, urlIsAbs),
   146  				),
   147  			},
   148  			checkHTTPVersionAttr: &schema.Schema{
   149  				Type:         schema.TypeString,
   150  				Optional:     true,
   151  				Default:      defaultCheckHTTPVersion,
   152  				ValidateFunc: validateStringIn(checkHTTPVersionAttr, supportedHTTPVersions),
   153  			},
   154  		}),
   155  	},
   156  }
   157  
   158  // checkAPIToStateHTTP reads the Config data out of circonusCheck.CheckBundle into the
   159  // statefile.
   160  func checkAPIToStateHTTP(c *circonusCheck, d *schema.ResourceData) error {
   161  	httpConfig := make(map[string]interface{}, len(c.Config))
   162  
   163  	// swamp is a sanity check: it must be empty by the time this method returns
   164  	swamp := make(map[config.Key]string, len(c.Config))
   165  	for k, v := range c.Config {
   166  		swamp[k] = v
   167  	}
   168  
   169  	saveStringConfigToState := func(apiKey config.Key, attrName schemaAttr) {
   170  		if v, ok := c.Config[apiKey]; ok {
   171  			httpConfig[string(attrName)] = v
   172  		}
   173  
   174  		delete(swamp, apiKey)
   175  	}
   176  
   177  	saveIntConfigToState := func(apiKey config.Key, attrName schemaAttr) {
   178  		if v, ok := c.Config[apiKey]; ok {
   179  			i, err := strconv.ParseInt(v, 10, 64)
   180  			if err != nil {
   181  				log.Printf("[ERROR]: Unable to convert %s to an integer: %v", apiKey, err)
   182  				return
   183  			}
   184  
   185  			httpConfig[string(attrName)] = int(i)
   186  		}
   187  
   188  		delete(swamp, apiKey)
   189  	}
   190  
   191  	saveStringConfigToState(config.AuthMethod, checkHTTPAuthMethodAttr)
   192  	saveStringConfigToState(config.AuthPassword, checkHTTPAuthPasswordAttr)
   193  	saveStringConfigToState(config.AuthUser, checkHTTPAuthUserAttr)
   194  	saveStringConfigToState(config.Body, checkHTTPBodyRegexpAttr)
   195  	saveStringConfigToState(config.CAChain, checkHTTPCAChainAttr)
   196  	saveStringConfigToState(config.CertFile, checkHTTPCertFileAttr)
   197  	saveStringConfigToState(config.Ciphers, checkHTTPCiphersAttr)
   198  	saveStringConfigToState(config.Code, checkHTTPCodeRegexpAttr)
   199  	saveStringConfigToState(config.Extract, checkHTTPExtractAttr)
   200  
   201  	headers := make(map[string]interface{}, len(c.Config))
   202  	headerPrefixLen := len(config.HeaderPrefix)
   203  	for k, v := range c.Config {
   204  		if len(k) <= headerPrefixLen {
   205  			continue
   206  		}
   207  
   208  		if strings.Compare(string(k[:headerPrefixLen]), string(config.HeaderPrefix)) == 0 {
   209  			key := k[headerPrefixLen:]
   210  			headers[string(key)] = v
   211  		}
   212  		delete(swamp, k)
   213  	}
   214  	httpConfig[string(checkHTTPHeadersAttr)] = headers
   215  
   216  	saveStringConfigToState(config.KeyFile, checkHTTPKeyFileAttr)
   217  	saveStringConfigToState(config.Method, checkHTTPMethodAttr)
   218  	saveStringConfigToState(config.Payload, checkHTTPPayloadAttr)
   219  	saveIntConfigToState(config.ReadLimit, checkHTTPReadLimitAttr)
   220  	saveStringConfigToState(config.URL, checkHTTPURLAttr)
   221  	saveStringConfigToState(config.HTTPVersion, checkHTTPVersionAttr)
   222  
   223  	whitelistedConfigKeys := map[config.Key]struct{}{
   224  		config.ReverseSecretKey: struct{}{},
   225  		config.SubmissionURL:    struct{}{},
   226  	}
   227  
   228  	for k := range swamp {
   229  		if _, ok := whitelistedConfigKeys[k]; ok {
   230  			delete(c.Config, k)
   231  		}
   232  
   233  		if _, ok := whitelistedConfigKeys[k]; !ok {
   234  			return fmt.Errorf("PROVIDER BUG: API Config not empty: %#v", swamp)
   235  		}
   236  	}
   237  
   238  	if err := d.Set(checkHTTPAttr, schema.NewSet(hashCheckHTTP, []interface{}{httpConfig})); err != nil {
   239  		return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkHTTPAttr), err)
   240  	}
   241  
   242  	return nil
   243  }
   244  
   245  // hashCheckHTTP creates a stable hash of the normalized values
   246  func hashCheckHTTP(v interface{}) int {
   247  	m := v.(map[string]interface{})
   248  	b := &bytes.Buffer{}
   249  	b.Grow(defaultHashBufSize)
   250  
   251  	writeInt := func(attrName schemaAttr) {
   252  		if v, ok := m[string(attrName)]; ok {
   253  			fmt.Fprintf(b, "%x", v.(int))
   254  		}
   255  	}
   256  
   257  	writeString := func(attrName schemaAttr) {
   258  		if v, ok := m[string(attrName)]; ok && v.(string) != "" {
   259  			fmt.Fprint(b, strings.TrimSpace(v.(string)))
   260  		}
   261  	}
   262  
   263  	// Order writes to the buffer using lexically sorted list for easy visual
   264  	// reconciliation with other lists.
   265  	writeString(checkHTTPAuthMethodAttr)
   266  	writeString(checkHTTPAuthPasswordAttr)
   267  	writeString(checkHTTPAuthUserAttr)
   268  	writeString(checkHTTPBodyRegexpAttr)
   269  	writeString(checkHTTPCAChainAttr)
   270  	writeString(checkHTTPCertFileAttr)
   271  	writeString(checkHTTPCiphersAttr)
   272  	writeString(checkHTTPCodeRegexpAttr)
   273  	writeString(checkHTTPExtractAttr)
   274  
   275  	if headersRaw, ok := m[string(checkHTTPHeadersAttr)]; ok {
   276  		headerMap := headersRaw.(map[string]interface{})
   277  		headers := make([]string, 0, len(headerMap))
   278  		for k := range headerMap {
   279  			headers = append(headers, k)
   280  		}
   281  
   282  		sort.Strings(headers)
   283  		for i := range headers {
   284  			fmt.Fprint(b, headers[i])
   285  			fmt.Fprint(b, headerMap[headers[i]].(string))
   286  		}
   287  	}
   288  
   289  	writeString(checkHTTPKeyFileAttr)
   290  	writeString(checkHTTPMethodAttr)
   291  	writeString(checkHTTPPayloadAttr)
   292  	writeInt(checkHTTPReadLimitAttr)
   293  	writeString(checkHTTPURLAttr)
   294  	writeString(checkHTTPVersionAttr)
   295  
   296  	s := b.String()
   297  	return hashcode.String(s)
   298  }
   299  
   300  func checkConfigToAPIHTTP(c *circonusCheck, l interfaceList) error {
   301  	c.Type = string(apiCheckTypeHTTP)
   302  
   303  	// Iterate over all `http` attributes, even though we have a max of 1 in the
   304  	// schema.
   305  	for _, mapRaw := range l {
   306  		httpConfig := newInterfaceMap(mapRaw)
   307  
   308  		if v, found := httpConfig[checkHTTPAuthMethodAttr]; found {
   309  			c.Config[config.AuthMethod] = v.(string)
   310  		}
   311  
   312  		if v, found := httpConfig[checkHTTPAuthPasswordAttr]; found {
   313  			c.Config[config.AuthPassword] = v.(string)
   314  		}
   315  
   316  		if v, found := httpConfig[checkHTTPAuthUserAttr]; found {
   317  			c.Config[config.AuthUser] = v.(string)
   318  		}
   319  
   320  		if v, found := httpConfig[checkHTTPBodyRegexpAttr]; found {
   321  			c.Config[config.Body] = v.(string)
   322  		}
   323  
   324  		if v, found := httpConfig[checkHTTPCAChainAttr]; found {
   325  			c.Config[config.CAChain] = v.(string)
   326  		}
   327  
   328  		if v, found := httpConfig[checkHTTPCertFileAttr]; found {
   329  			c.Config[config.CertFile] = v.(string)
   330  		}
   331  
   332  		if v, found := httpConfig[checkHTTPCiphersAttr]; found {
   333  			c.Config[config.Ciphers] = v.(string)
   334  		}
   335  
   336  		if v, found := httpConfig[checkHTTPCodeRegexpAttr]; found {
   337  			c.Config[config.Code] = v.(string)
   338  		}
   339  
   340  		if v, found := httpConfig[checkHTTPExtractAttr]; found {
   341  			c.Config[config.Extract] = v.(string)
   342  		}
   343  
   344  		if headers := httpConfig.CollectMap(checkHTTPHeadersAttr); headers != nil {
   345  			for k, v := range headers {
   346  				h := config.HeaderPrefix + config.Key(k)
   347  				c.Config[h] = v
   348  			}
   349  		}
   350  
   351  		if v, found := httpConfig[checkHTTPKeyFileAttr]; found {
   352  			c.Config[config.KeyFile] = v.(string)
   353  		}
   354  
   355  		if v, found := httpConfig[checkHTTPMethodAttr]; found {
   356  			c.Config[config.Method] = v.(string)
   357  		}
   358  
   359  		if v, found := httpConfig[checkHTTPPayloadAttr]; found {
   360  			c.Config[config.Payload] = v.(string)
   361  		}
   362  
   363  		if v, found := httpConfig[checkHTTPReadLimitAttr]; found {
   364  			c.Config[config.ReadLimit] = fmt.Sprintf("%d", v.(int))
   365  		}
   366  
   367  		if v, found := httpConfig[checkHTTPURLAttr]; found {
   368  			c.Config[config.URL] = v.(string)
   369  
   370  			u, _ := url.Parse(v.(string))
   371  			hostInfo := strings.SplitN(u.Host, ":", 2)
   372  			if len(c.Target) == 0 {
   373  				c.Target = hostInfo[0]
   374  			}
   375  
   376  			if len(hostInfo) > 1 && c.Config[config.Port] == "" {
   377  				c.Config[config.Port] = hostInfo[1]
   378  			}
   379  		}
   380  
   381  		if v, found := httpConfig[checkHTTPVersionAttr]; found {
   382  			c.Config[config.HTTPVersion] = v.(string)
   383  		}
   384  	}
   385  
   386  	return nil
   387  }