github.com/peterbale/terraform@v0.9.0-beta2.0.20170315142748-5723acd55547/builtin/providers/circonus/resource_circonus_check.go (about) 1 package circonus 2 3 /* 4 * Note to future readers: The `circonus_check` resource is actually a facade for 5 * the check_bundle call. check_bundle is an implementation detail that we mask 6 * over and expose just a "check" even though the "check" is actually a 7 * check_bundle. 8 * 9 * Style note: There are three directions that information flows: 10 * 11 * 1) Terraform Config file into API Objects. *Attr named objects are Config or 12 * Schema attribute names. In this file, all config constants should be 13 * named check*Attr. 14 * 15 * 2) API Objects into Statefile data. api*Attr named constants are parameters 16 * that originate from the API and need to be mapped into the provider's 17 * vernacular. 18 */ 19 20 import ( 21 "fmt" 22 "time" 23 24 "github.com/circonus-labs/circonus-gometrics/api" 25 "github.com/circonus-labs/circonus-gometrics/api/config" 26 "github.com/hashicorp/errwrap" 27 "github.com/hashicorp/terraform/helper/schema" 28 ) 29 30 const ( 31 // circonus_check.* global resource attribute names 32 checkActiveAttr = "active" 33 checkCAQLAttr = "caql" 34 checkCloudWatchAttr = "cloudwatch" 35 checkCollectorAttr = "collector" 36 checkHTTPAttr = "http" 37 checkHTTPTrapAttr = "httptrap" 38 checkICMPPingAttr = "icmp_ping" 39 checkJSONAttr = "json" 40 checkMetricLimitAttr = "metric_limit" 41 checkMySQLAttr = "mysql" 42 checkNameAttr = "name" 43 checkNotesAttr = "notes" 44 checkPeriodAttr = "period" 45 checkPostgreSQLAttr = "postgresql" 46 checkMetricAttr = "metric" 47 checkTagsAttr = "tags" 48 checkTargetAttr = "target" 49 checkTCPAttr = "tcp" 50 checkTimeoutAttr = "timeout" 51 checkTypeAttr = "type" 52 53 // circonus_check.collector.* resource attribute names 54 checkCollectorIDAttr = "id" 55 56 // circonus_check.metric.* resource attribute names are aliased to 57 // circonus_metric.* resource attributes. 58 59 // circonus_check.metric.* resource attribute names 60 // metricIDAttr = "id" 61 62 // Out parameters for circonus_check 63 checkOutByCollectorAttr = "check_by_collector" 64 checkOutCheckUUIDsAttr = "uuids" 65 checkOutChecksAttr = "checks" 66 checkOutCreatedAttr = "created" 67 checkOutLastModifiedAttr = "last_modified" 68 checkOutLastModifiedByAttr = "last_modified_by" 69 checkOutReverseConnectURLsAttr = "reverse_connect_urls" 70 ) 71 72 const ( 73 // Circonus API constants from their API endpoints 74 apiCheckTypeCAQLAttr apiCheckType = "caql" 75 apiCheckTypeCloudWatchAttr apiCheckType = "cloudwatch" 76 apiCheckTypeHTTPAttr apiCheckType = "http" 77 apiCheckTypeHTTPTrapAttr apiCheckType = "httptrap" 78 apiCheckTypeICMPPingAttr apiCheckType = "ping_icmp" 79 apiCheckTypeJSONAttr apiCheckType = "json" 80 apiCheckTypeMySQLAttr apiCheckType = "mysql" 81 apiCheckTypePostgreSQLAttr apiCheckType = "postgres" 82 apiCheckTypeTCPAttr apiCheckType = "tcp" 83 ) 84 85 var checkDescriptions = attrDescrs{ 86 checkActiveAttr: "If the check is activate or disabled", 87 checkCAQLAttr: "CAQL check configuration", 88 checkCloudWatchAttr: "CloudWatch check configuration", 89 checkCollectorAttr: "The collector(s) that are responsible for gathering the metrics", 90 checkHTTPAttr: "HTTP check configuration", 91 checkHTTPTrapAttr: "HTTP Trap check configuration", 92 checkICMPPingAttr: "ICMP ping check configuration", 93 checkJSONAttr: "JSON check configuration", 94 checkMetricLimitAttr: `Setting a metric_limit will enable all (-1), disable (0), or allow up to the specified limit of metrics for this check ("N+", where N is a positive integer)`, 95 checkMySQLAttr: "MySQL check configuration", 96 checkNameAttr: "The name of the check bundle that will be displayed in the web interface", 97 checkNotesAttr: "Notes about this check bundle", 98 checkPeriodAttr: "The period between each time the check is made", 99 checkPostgreSQLAttr: "PostgreSQL check configuration", 100 checkMetricAttr: "Configuration for a stream of metrics", 101 checkTagsAttr: "A list of tags assigned to the check", 102 checkTargetAttr: "The target of the check (e.g. hostname, URL, IP, etc)", 103 checkTCPAttr: "TCP check configuration", 104 checkTimeoutAttr: "The length of time in seconds (and fractions of a second) before the check will timeout if no response is returned to the collector", 105 checkTypeAttr: "The check type", 106 107 checkOutChecksAttr: "", 108 checkOutByCollectorAttr: "", 109 checkOutCheckUUIDsAttr: "", 110 checkOutCreatedAttr: "", 111 checkOutLastModifiedAttr: "", 112 checkOutLastModifiedByAttr: "", 113 checkOutReverseConnectURLsAttr: "", 114 } 115 116 var checkCollectorDescriptions = attrDescrs{ 117 checkCollectorIDAttr: "The ID of the collector", 118 } 119 120 var checkMetricDescriptions = metricDescriptions 121 122 func resourceCheck() *schema.Resource { 123 return &schema.Resource{ 124 Create: checkCreate, 125 Read: checkRead, 126 Update: checkUpdate, 127 Delete: checkDelete, 128 Exists: checkExists, 129 Importer: &schema.ResourceImporter{ 130 State: schema.ImportStatePassthrough, 131 }, 132 133 Schema: convertToHelperSchema(checkDescriptions, map[schemaAttr]*schema.Schema{ 134 checkActiveAttr: &schema.Schema{ 135 Type: schema.TypeBool, 136 Optional: true, 137 Default: true, 138 }, 139 checkCAQLAttr: schemaCheckCAQL, 140 checkCloudWatchAttr: schemaCheckCloudWatch, 141 checkCollectorAttr: &schema.Schema{ 142 Type: schema.TypeSet, 143 Optional: true, 144 MinItems: 1, 145 Elem: &schema.Resource{ 146 Schema: convertToHelperSchema(checkCollectorDescriptions, map[schemaAttr]*schema.Schema{ 147 checkCollectorIDAttr: &schema.Schema{ 148 Type: schema.TypeString, 149 Required: true, 150 ValidateFunc: validateRegexp(checkCollectorIDAttr, config.BrokerCIDRegex), 151 }, 152 }), 153 }, 154 }, 155 checkHTTPAttr: schemaCheckHTTP, 156 checkHTTPTrapAttr: schemaCheckHTTPTrap, 157 checkJSONAttr: schemaCheckJSON, 158 checkICMPPingAttr: schemaCheckICMPPing, 159 checkMetricLimitAttr: &schema.Schema{ 160 Type: schema.TypeInt, 161 Optional: true, 162 Computed: true, 163 ValidateFunc: validateFuncs( 164 validateIntMin(checkMetricLimitAttr, -1), 165 ), 166 }, 167 checkMySQLAttr: schemaCheckMySQL, 168 checkNameAttr: &schema.Schema{ 169 Type: schema.TypeString, 170 Optional: true, 171 Computed: true, 172 }, 173 checkNotesAttr: &schema.Schema{ 174 Type: schema.TypeString, 175 Optional: true, 176 Computed: true, 177 StateFunc: suppressWhitespace, 178 }, 179 checkPeriodAttr: &schema.Schema{ 180 Type: schema.TypeString, 181 Optional: true, 182 Computed: true, 183 StateFunc: normalizeTimeDurationStringToSeconds, 184 ValidateFunc: validateFuncs( 185 validateDurationMin(checkPeriodAttr, defaultCirconusCheckPeriodMin), 186 validateDurationMax(checkPeriodAttr, defaultCirconusCheckPeriodMax), 187 ), 188 }, 189 checkPostgreSQLAttr: schemaCheckPostgreSQL, 190 checkMetricAttr: &schema.Schema{ 191 Type: schema.TypeSet, 192 Optional: true, 193 Set: checkMetricChecksum, 194 MinItems: 1, 195 Elem: &schema.Resource{ 196 Schema: convertToHelperSchema(checkMetricDescriptions, map[schemaAttr]*schema.Schema{ 197 metricActiveAttr: &schema.Schema{ 198 Type: schema.TypeBool, 199 Optional: true, 200 Default: true, 201 }, 202 metricNameAttr: &schema.Schema{ 203 Type: schema.TypeString, 204 Required: true, 205 ValidateFunc: validateRegexp(metricNameAttr, `[\S]+`), 206 }, 207 metricTagsAttr: tagMakeConfigSchema(metricTagsAttr), 208 metricTypeAttr: &schema.Schema{ 209 Type: schema.TypeString, 210 Required: true, 211 ValidateFunc: validateMetricType, 212 }, 213 metricUnitAttr: &schema.Schema{ 214 Type: schema.TypeString, 215 Optional: true, 216 Default: metricUnit, 217 ValidateFunc: validateRegexp(metricUnitAttr, metricUnitRegexp), 218 }, 219 }), 220 }, 221 }, 222 checkTagsAttr: tagMakeConfigSchema(checkTagsAttr), 223 checkTargetAttr: &schema.Schema{ 224 Type: schema.TypeString, 225 Optional: true, 226 Computed: true, 227 ValidateFunc: validateRegexp(checkTagsAttr, `.+`), 228 }, 229 checkTCPAttr: schemaCheckTCP, 230 checkTimeoutAttr: &schema.Schema{ 231 Type: schema.TypeString, 232 Optional: true, 233 Computed: true, 234 StateFunc: normalizeTimeDurationStringToSeconds, 235 ValidateFunc: validateFuncs( 236 validateDurationMin(checkTimeoutAttr, defaultCirconusTimeoutMin), 237 validateDurationMax(checkTimeoutAttr, defaultCirconusTimeoutMax), 238 ), 239 }, 240 checkTypeAttr: &schema.Schema{ 241 Type: schema.TypeString, 242 Computed: true, 243 Optional: true, 244 ForceNew: true, 245 ValidateFunc: validateCheckType, 246 }, 247 248 // Out parameters 249 checkOutByCollectorAttr: &schema.Schema{ 250 Type: schema.TypeMap, 251 Computed: true, 252 Elem: &schema.Schema{ 253 Type: schema.TypeString, 254 }, 255 }, 256 checkOutCheckUUIDsAttr: &schema.Schema{ 257 Type: schema.TypeList, 258 Computed: true, 259 Elem: &schema.Schema{ 260 Type: schema.TypeString, 261 }, 262 }, 263 checkOutChecksAttr: &schema.Schema{ 264 Type: schema.TypeList, 265 Computed: true, 266 Elem: &schema.Schema{ 267 Type: schema.TypeString, 268 }, 269 }, 270 checkOutCreatedAttr: &schema.Schema{ 271 Type: schema.TypeInt, 272 Computed: true, 273 }, 274 checkOutLastModifiedAttr: &schema.Schema{ 275 Type: schema.TypeInt, 276 Computed: true, 277 }, 278 checkOutLastModifiedByAttr: &schema.Schema{ 279 Type: schema.TypeString, 280 Computed: true, 281 }, 282 checkOutReverseConnectURLsAttr: &schema.Schema{ 283 Type: schema.TypeList, 284 Computed: true, 285 Elem: &schema.Schema{ 286 Type: schema.TypeString, 287 }, 288 }, 289 }), 290 } 291 } 292 293 func checkCreate(d *schema.ResourceData, meta interface{}) error { 294 ctxt := meta.(*providerContext) 295 c := newCheck() 296 if err := c.ParseConfig(d); err != nil { 297 return errwrap.Wrapf("error parsing check schema during create: {{err}}", err) 298 } 299 300 if err := c.Create(ctxt); err != nil { 301 return errwrap.Wrapf("error creating check: {{err}}", err) 302 } 303 304 d.SetId(c.CID) 305 306 return checkRead(d, meta) 307 } 308 309 func checkExists(d *schema.ResourceData, meta interface{}) (bool, error) { 310 ctxt := meta.(*providerContext) 311 312 cid := d.Id() 313 cb, err := ctxt.client.FetchCheckBundle(api.CIDType(&cid)) 314 if err != nil { 315 return false, err 316 } 317 318 if cb.CID == "" { 319 return false, nil 320 } 321 322 return true, nil 323 } 324 325 // checkRead pulls data out of the CheckBundle object and stores it into the 326 // appropriate place in the statefile. 327 func checkRead(d *schema.ResourceData, meta interface{}) error { 328 ctxt := meta.(*providerContext) 329 330 cid := d.Id() 331 c, err := loadCheck(ctxt, api.CIDType(&cid)) 332 if err != nil { 333 return err 334 } 335 336 d.SetId(c.CID) 337 338 // Global circonus_check attributes are saved first, followed by the check 339 // type specific attributes handled below in their respective checkRead*(). 340 341 checkIDsByCollector := make(map[string]interface{}, len(c.Checks)) 342 for i, b := range c.Brokers { 343 checkIDsByCollector[b] = c.Checks[i] 344 } 345 346 metrics := schema.NewSet(checkMetricChecksum, nil) 347 for _, m := range c.Metrics { 348 metricAttrs := map[string]interface{}{ 349 string(metricActiveAttr): metricAPIStatusToBool(m.Status), 350 string(metricNameAttr): m.Name, 351 string(metricTagsAttr): tagsToState(apiToTags(m.Tags)), 352 string(metricTypeAttr): m.Type, 353 string(metricUnitAttr): indirect(m.Units), 354 } 355 356 metrics.Add(metricAttrs) 357 } 358 359 // Write the global circonus_check parameters followed by the check 360 // type-specific parameters. 361 362 d.Set(checkActiveAttr, checkAPIStatusToBool(c.Status)) 363 364 if err := d.Set(checkCollectorAttr, stringListToSet(c.Brokers, checkCollectorIDAttr)); err != nil { 365 return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkCollectorAttr), err) 366 } 367 368 d.Set(checkMetricLimitAttr, c.MetricLimit) 369 d.Set(checkNameAttr, c.DisplayName) 370 d.Set(checkNotesAttr, c.Notes) 371 d.Set(checkPeriodAttr, fmt.Sprintf("%ds", c.Period)) 372 373 if err := d.Set(checkMetricAttr, metrics); err != nil { 374 return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkMetricAttr), err) 375 } 376 377 if err := d.Set(checkTagsAttr, c.Tags); err != nil { 378 return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkTagsAttr), err) 379 } 380 381 d.Set(checkTargetAttr, c.Target) 382 383 { 384 t, _ := time.ParseDuration(fmt.Sprintf("%fs", c.Timeout)) 385 d.Set(checkTimeoutAttr, t.String()) 386 } 387 388 d.Set(checkTypeAttr, c.Type) 389 390 // Last step: parse a check_bundle's config into the statefile. 391 if err := parseCheckTypeConfig(&c, d); err != nil { 392 return errwrap.Wrapf("Unable to parse check config: {{err}}", err) 393 } 394 395 // Out parameters 396 if err := d.Set(checkOutByCollectorAttr, checkIDsByCollector); err != nil { 397 return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkOutByCollectorAttr), err) 398 } 399 400 if err := d.Set(checkOutCheckUUIDsAttr, c.CheckUUIDs); err != nil { 401 return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkOutCheckUUIDsAttr), err) 402 } 403 404 if err := d.Set(checkOutChecksAttr, c.Checks); err != nil { 405 return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkOutChecksAttr), err) 406 } 407 408 d.Set(checkOutCreatedAttr, c.Created) 409 d.Set(checkOutLastModifiedAttr, c.LastModified) 410 d.Set(checkOutLastModifiedByAttr, c.LastModifedBy) 411 412 if err := d.Set(checkOutReverseConnectURLsAttr, c.ReverseConnectURLs); err != nil { 413 return errwrap.Wrapf(fmt.Sprintf("Unable to store check %q attribute: {{err}}", checkOutReverseConnectURLsAttr), err) 414 } 415 416 return nil 417 } 418 419 func checkUpdate(d *schema.ResourceData, meta interface{}) error { 420 ctxt := meta.(*providerContext) 421 c := newCheck() 422 if err := c.ParseConfig(d); err != nil { 423 return err 424 } 425 426 c.CID = d.Id() 427 if err := c.Update(ctxt); err != nil { 428 return errwrap.Wrapf(fmt.Sprintf("unable to update check %q: {{err}}", d.Id()), err) 429 } 430 431 return checkRead(d, meta) 432 } 433 434 func checkDelete(d *schema.ResourceData, meta interface{}) error { 435 ctxt := meta.(*providerContext) 436 437 if _, err := ctxt.client.Delete(d.Id()); err != nil { 438 return errwrap.Wrapf(fmt.Sprintf("unable to delete check %q: {{err}}", d.Id()), err) 439 } 440 441 d.SetId("") 442 443 return nil 444 } 445 446 func checkMetricChecksum(v interface{}) int { 447 m := v.(map[string]interface{}) 448 csum := metricChecksum(m) 449 return csum 450 } 451 452 // ParseConfig reads Terraform config data and stores the information into a 453 // Circonus CheckBundle object. 454 func (c *circonusCheck) ParseConfig(d *schema.ResourceData) error { 455 if v, found := d.GetOk(checkActiveAttr); found { 456 c.Status = checkActiveToAPIStatus(v.(bool)) 457 } 458 459 if v, found := d.GetOk(checkCollectorAttr); found { 460 l := v.(*schema.Set).List() 461 c.Brokers = make([]string, 0, len(l)) 462 463 for _, mapRaw := range l { 464 mapAttrs := mapRaw.(map[string]interface{}) 465 466 if mv, mapFound := mapAttrs[checkCollectorIDAttr]; mapFound { 467 c.Brokers = append(c.Brokers, mv.(string)) 468 } 469 } 470 } 471 472 if v, found := d.GetOk(checkMetricLimitAttr); found { 473 c.MetricLimit = v.(int) 474 } 475 476 if v, found := d.GetOk(checkNameAttr); found { 477 c.DisplayName = v.(string) 478 } 479 480 if v, found := d.GetOk(checkNotesAttr); found { 481 s := v.(string) 482 c.Notes = &s 483 } 484 485 if v, found := d.GetOk(checkPeriodAttr); found { 486 d, err := time.ParseDuration(v.(string)) 487 if err != nil { 488 return errwrap.Wrapf(fmt.Sprintf("unable to parse %q as a duration: {{err}}", checkPeriodAttr), err) 489 } 490 491 c.Period = uint(d.Seconds()) 492 } 493 494 if v, found := d.GetOk(checkMetricAttr); found { 495 metricList := v.(*schema.Set).List() 496 c.Metrics = make([]api.CheckBundleMetric, 0, len(metricList)) 497 498 for _, metricListRaw := range metricList { 499 metricAttrs := metricListRaw.(map[string]interface{}) 500 501 var id string 502 if av, found := metricAttrs[metricIDAttr]; found { 503 id = av.(string) 504 } else { 505 var err error 506 id, err = newMetricID() 507 if err != nil { 508 return errwrap.Wrapf("unable to create a new metric ID: {{err}}", err) 509 } 510 } 511 512 m := newMetric() 513 if err := m.ParseConfigMap(id, metricAttrs); err != nil { 514 return errwrap.Wrapf("unable to parse config: {{err}}", err) 515 } 516 517 c.Metrics = append(c.Metrics, m.CheckBundleMetric) 518 } 519 } 520 521 if v, found := d.GetOk(checkTagsAttr); found { 522 c.Tags = derefStringList(flattenSet(v.(*schema.Set))) 523 } 524 525 if v, found := d.GetOk(checkTargetAttr); found { 526 c.Target = v.(string) 527 } 528 529 if v, found := d.GetOk(checkTimeoutAttr); found { 530 d, err := time.ParseDuration(v.(string)) 531 if err != nil { 532 return errwrap.Wrapf(fmt.Sprintf("unable to parse %q as a duration: {{err}}", checkTimeoutAttr), err) 533 } 534 535 t := float32(d.Seconds()) 536 c.Timeout = t 537 } 538 539 // Last step: parse the individual check types 540 if err := checkConfigToAPI(c, d); err != nil { 541 return errwrap.Wrapf("unable to parse check type: {{err}}", err) 542 } 543 544 if err := c.Fixup(); err != nil { 545 return err 546 } 547 548 if err := c.Validate(); err != nil { 549 return err 550 } 551 552 return nil 553 } 554 555 // checkConfigToAPI parses the Terraform config into the respective per-check 556 // type api.Config attributes. 557 func checkConfigToAPI(c *circonusCheck, d *schema.ResourceData) error { 558 checkTypeParseMap := map[string]func(*circonusCheck, interfaceList) error{ 559 checkCAQLAttr: checkConfigToAPICAQL, 560 checkCloudWatchAttr: checkConfigToAPICloudWatch, 561 checkHTTPAttr: checkConfigToAPIHTTP, 562 checkHTTPTrapAttr: checkConfigToAPIHTTPTrap, 563 checkICMPPingAttr: checkConfigToAPIICMPPing, 564 checkJSONAttr: checkConfigToAPIJSON, 565 checkMySQLAttr: checkConfigToAPIMySQL, 566 checkPostgreSQLAttr: checkConfigToAPIPostgreSQL, 567 checkTCPAttr: checkConfigToAPITCP, 568 } 569 570 for checkType, fn := range checkTypeParseMap { 571 if listRaw, found := d.GetOk(checkType); found { 572 if err := fn(c, listRaw.(*schema.Set).List()); err != nil { 573 return errwrap.Wrapf(fmt.Sprintf("Unable to parse type %q: {{err}}", string(checkType)), err) 574 } 575 } 576 } 577 578 return nil 579 } 580 581 // parseCheckTypeConfig parses an API Config object and stores the result in the 582 // statefile. 583 func parseCheckTypeConfig(c *circonusCheck, d *schema.ResourceData) error { 584 checkTypeConfigHandlers := map[apiCheckType]func(*circonusCheck, *schema.ResourceData) error{ 585 apiCheckTypeCAQLAttr: checkAPIToStateCAQL, 586 apiCheckTypeCloudWatchAttr: checkAPIToStateCloudWatch, 587 apiCheckTypeHTTPAttr: checkAPIToStateHTTP, 588 apiCheckTypeHTTPTrapAttr: checkAPIToStateHTTPTrap, 589 apiCheckTypeICMPPingAttr: checkAPIToStateICMPPing, 590 apiCheckTypeJSONAttr: checkAPIToStateJSON, 591 apiCheckTypeMySQLAttr: checkAPIToStateMySQL, 592 apiCheckTypePostgreSQLAttr: checkAPIToStatePostgreSQL, 593 apiCheckTypeTCPAttr: checkAPIToStateTCP, 594 } 595 596 var checkType apiCheckType = apiCheckType(c.Type) 597 fn, ok := checkTypeConfigHandlers[checkType] 598 if !ok { 599 return fmt.Errorf("check type %q not supported", c.Type) 600 } 601 602 if err := fn(c, d); err != nil { 603 return errwrap.Wrapf(fmt.Sprintf("unable to parse the API config for %q: {{err}}", c.Type), err) 604 } 605 606 return nil 607 }