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