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 }