github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/builtin/providers/datadog/resource_datadog_timeboard.go (about) 1 package datadog 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "strconv" 8 "strings" 9 10 "github.com/hashicorp/terraform/helper/schema" 11 "github.com/zorkian/go-datadog-api" 12 ) 13 14 func resourceDatadogTimeboard() *schema.Resource { 15 request := &schema.Schema{ 16 Type: schema.TypeList, 17 Required: true, 18 Elem: &schema.Resource{ 19 Schema: map[string]*schema.Schema{ 20 "q": &schema.Schema{ 21 Type: schema.TypeString, 22 Required: true, 23 }, 24 "stacked": &schema.Schema{ 25 Type: schema.TypeBool, 26 Optional: true, 27 Default: false, 28 }, 29 "type": &schema.Schema{ 30 Type: schema.TypeString, 31 Optional: true, 32 Default: "line", 33 }, 34 "aggregator": { 35 Type: schema.TypeString, 36 Optional: true, 37 ValidateFunc: validateAggregatorMethod, 38 }, 39 "style": &schema.Schema{ 40 Type: schema.TypeMap, 41 Optional: true, 42 }, 43 "conditional_format": &schema.Schema{ 44 Type: schema.TypeList, 45 Optional: true, 46 Description: "A list of conditional formatting rules.", 47 Elem: &schema.Resource{ 48 Schema: map[string]*schema.Schema{ 49 "palette": &schema.Schema{ 50 Type: schema.TypeString, 51 Optional: true, 52 Description: "The palette to use if this condition is met.", 53 }, 54 "comparator": &schema.Schema{ 55 Type: schema.TypeString, 56 Required: true, 57 Description: "Comparator (<, >, etc)", 58 }, 59 "custom_bg_color": &schema.Schema{ 60 Type: schema.TypeString, 61 Optional: true, 62 Description: "Custom background color (e.g., #205081)", 63 }, 64 "value": &schema.Schema{ 65 Type: schema.TypeString, 66 Optional: true, 67 Description: "Value that is threshold for conditional format", 68 }, 69 "custom_fg_color": &schema.Schema{ 70 Type: schema.TypeString, 71 Optional: true, 72 Description: "Custom foreground color (e.g., #59afe1)", 73 }, 74 }, 75 }, 76 }, 77 "change_type": &schema.Schema{ 78 Type: schema.TypeString, 79 Optional: true, 80 Description: "Type of change for change graphs.", 81 }, 82 "order_direction": &schema.Schema{ 83 Type: schema.TypeString, 84 Optional: true, 85 Description: "Sort change graph in ascending or descending order.", 86 }, 87 "compare_to": &schema.Schema{ 88 Type: schema.TypeString, 89 Optional: true, 90 Description: "The time period to compare change against in change graphs.", 91 }, 92 "increase_good": &schema.Schema{ 93 Type: schema.TypeBool, 94 Optional: true, 95 Description: "Decides whether to represent increases as good or bad in change graphs.", 96 }, 97 "order_by": &schema.Schema{ 98 Type: schema.TypeString, 99 Optional: true, 100 Description: "The field a change graph will be ordered by.", 101 }, 102 "extra_col": &schema.Schema{ 103 Type: schema.TypeString, 104 Optional: true, 105 Description: "If set to 'present', this will include the present values in change graphs.", 106 }, 107 }, 108 }, 109 } 110 111 marker := &schema.Schema{ 112 Type: schema.TypeList, 113 Optional: true, 114 Elem: &schema.Resource{ 115 Schema: map[string]*schema.Schema{ 116 "type": &schema.Schema{ 117 Type: schema.TypeString, 118 Required: true, 119 }, 120 "value": &schema.Schema{ 121 Type: schema.TypeString, 122 Required: true, 123 }, 124 "label": &schema.Schema{ 125 Type: schema.TypeString, 126 Optional: true, 127 }, 128 }, 129 }, 130 } 131 132 graph := &schema.Schema{ 133 Type: schema.TypeList, 134 Required: true, 135 Description: "A list of graph definitions.", 136 Elem: &schema.Resource{ 137 Schema: map[string]*schema.Schema{ 138 "title": &schema.Schema{ 139 Type: schema.TypeString, 140 Required: true, 141 Description: "The name of the graph.", 142 }, 143 "events": &schema.Schema{ 144 Type: schema.TypeSet, 145 Optional: true, 146 Description: "Filter for events to be overlayed on the graph.", 147 Elem: &schema.Schema{ 148 Type: schema.TypeString, 149 }, 150 }, 151 "viz": &schema.Schema{ 152 Type: schema.TypeString, 153 Required: true, 154 }, 155 "request": request, 156 "marker": marker, 157 "yaxis": &schema.Schema{ 158 Type: schema.TypeMap, 159 Optional: true, 160 }, 161 "autoscale": &schema.Schema{ 162 Type: schema.TypeBool, 163 Optional: true, 164 Description: "Automatically scale graphs", 165 }, 166 "text_align": &schema.Schema{ 167 Type: schema.TypeString, 168 Optional: true, 169 Description: "How to align text", 170 }, 171 "precision": &schema.Schema{ 172 Type: schema.TypeString, 173 Optional: true, 174 Description: "How many digits to show", 175 }, 176 "custom_unit": &schema.Schema{ 177 Type: schema.TypeString, 178 Optional: true, 179 Description: "Use a custom unit (like 'users')", 180 }, 181 "style": &schema.Schema{ 182 Type: schema.TypeMap, 183 Optional: true, 184 }, 185 "group": &schema.Schema{ 186 Type: schema.TypeSet, 187 Optional: true, 188 Description: "A list of groupings for hostmap type graphs.", 189 Elem: &schema.Schema{ 190 Type: schema.TypeString, 191 }, 192 }, 193 "include_no_metric_hosts": &schema.Schema{ 194 Type: schema.TypeBool, 195 Optional: true, 196 Description: "Include hosts without metrics in hostmap graphs", 197 }, 198 "scope": &schema.Schema{ 199 Type: schema.TypeSet, 200 Optional: true, 201 Description: "A list of scope filters for hostmap type graphs.", 202 Elem: &schema.Schema{ 203 Type: schema.TypeString, 204 }, 205 }, 206 "include_ungrouped_hosts": &schema.Schema{ 207 Type: schema.TypeBool, 208 Optional: true, 209 Description: "Include ungrouped hosts in hostmap graphs", 210 }, 211 }, 212 }, 213 } 214 215 template_variable := &schema.Schema{ 216 Type: schema.TypeList, 217 Optional: true, 218 Description: "A list of template variables for using Dashboard templating.", 219 Elem: &schema.Resource{ 220 Schema: map[string]*schema.Schema{ 221 "name": &schema.Schema{ 222 Type: schema.TypeString, 223 Required: true, 224 Description: "The name of the variable.", 225 }, 226 "prefix": &schema.Schema{ 227 Type: schema.TypeString, 228 Optional: true, 229 Description: "The tag prefix associated with the variable. Only tags with this prefix will appear in the variable dropdown.", 230 }, 231 "default": &schema.Schema{ 232 Type: schema.TypeString, 233 Optional: true, 234 Description: "The default value for the template variable on dashboard load.", 235 }, 236 }, 237 }, 238 } 239 240 return &schema.Resource{ 241 Create: resourceDatadogTimeboardCreate, 242 Update: resourceDatadogTimeboardUpdate, 243 Read: resourceDatadogTimeboardRead, 244 Delete: resourceDatadogTimeboardDelete, 245 Exists: resourceDatadogTimeboardExists, 246 Importer: &schema.ResourceImporter{ 247 State: resourceDatadogTimeboardImport, 248 }, 249 250 Schema: map[string]*schema.Schema{ 251 "title": &schema.Schema{ 252 Type: schema.TypeString, 253 Required: true, 254 Description: "The name of the dashboard.", 255 }, 256 "description": &schema.Schema{ 257 Type: schema.TypeString, 258 Required: true, 259 Description: "A description of the dashboard's content.", 260 }, 261 "read_only": &schema.Schema{ 262 Type: schema.TypeBool, 263 Optional: true, 264 Default: false, 265 }, 266 "graph": graph, 267 "template_variable": template_variable, 268 }, 269 } 270 } 271 272 func appendConditionalFormats(datadogRequest *datadog.GraphDefinitionRequest, terraformFormats *[]interface{}) { 273 for _, t_ := range *terraformFormats { 274 t := t_.(map[string]interface{}) 275 d := datadog.DashboardConditionalFormat{ 276 Comparator: t["comparator"].(string), 277 } 278 279 if palette, ok := t["palette"]; ok { 280 d.Palette = palette.(string) 281 } 282 283 if customBgColor, ok := t["custom_bg_color"]; ok { 284 d.CustomBgColor = customBgColor.(string) 285 } 286 287 if customFgColor, ok := t["custom_fg_color"]; ok { 288 d.CustomFgColor = customFgColor.(string) 289 } 290 291 if value, ok := t["value"]; ok { 292 d.Value = json.Number(value.(string)) 293 } 294 295 datadogRequest.ConditionalFormats = append(datadogRequest.ConditionalFormats, d) 296 } 297 } 298 299 func buildTemplateVariables(terraformTemplateVariables *[]interface{}) *[]datadog.TemplateVariable { 300 datadogTemplateVariables := make([]datadog.TemplateVariable, len(*terraformTemplateVariables)) 301 for i, t_ := range *terraformTemplateVariables { 302 t := t_.(map[string]interface{}) 303 datadogTemplateVariables[i] = datadog.TemplateVariable{ 304 Name: t["name"].(string), 305 Prefix: t["prefix"].(string), 306 Default: t["default"].(string)} 307 } 308 return &datadogTemplateVariables 309 } 310 311 func appendRequests(datadogGraph *datadog.Graph, terraformRequests *[]interface{}) { 312 for _, t_ := range *terraformRequests { 313 t := t_.(map[string]interface{}) 314 d := datadog.GraphDefinitionRequest{ 315 Query: t["q"].(string), 316 Type: t["type"].(string), 317 Aggregator: t["aggregator"].(string), 318 } 319 if stacked, ok := t["stacked"]; ok { 320 d.Stacked = stacked.(bool) 321 } 322 if style, ok := t["style"]; ok { 323 s, _ := style.(map[string]interface{}) 324 325 style := struct { 326 Palette *string `json:"palette,omitempty"` 327 Width *string `json:"width,omitempty"` 328 Type *string `json:"type,omitempty"` 329 }{} 330 331 if palette_, ok := s["palette"]; ok { 332 palette := palette_.(string) 333 style.Palette = &palette 334 } 335 336 if width, ok := s["width"]; ok { 337 width := width.(string) 338 style.Width = &width 339 } 340 341 if type_, ok := s["type"]; ok { 342 style_type := type_.(string) 343 style.Type = &style_type 344 } 345 346 d.Style = &style 347 } 348 349 if changeType, ok := t["change_type"]; ok { 350 d.ChangeType = changeType.(string) 351 } 352 if compareTo, ok := t["compare_to"]; ok { 353 d.CompareTo = compareTo.(string) 354 } 355 if increaseGood, ok := t["increase_good"]; ok { 356 d.IncreaseGood = increaseGood.(bool) 357 } 358 if orderBy, ok := t["order_by"]; ok { 359 d.OrderBy = orderBy.(string) 360 } 361 if extraCol, ok := t["extra_col"]; ok { 362 d.ExtraCol = extraCol.(string) 363 } 364 if orderDirection, ok := t["order_direction"]; ok { 365 d.OrderDirection = orderDirection.(string) 366 } 367 368 if terraformConditionalFormats, ok := t["conditional_format"]; ok { 369 formats := terraformConditionalFormats.([]interface{}) 370 appendConditionalFormats(&d, &formats) 371 } 372 373 datadogGraph.Definition.Requests = append(datadogGraph.Definition.Requests, d) 374 } 375 } 376 377 func appendEvents(datadogGraph *datadog.Graph, terraformEvents *[]interface{}) { 378 for _, t_ := range *terraformEvents { 379 d := struct { 380 Query string `json:"q"` 381 }{ 382 t_.(string), 383 } 384 datadogGraph.Definition.Events = append(datadogGraph.Definition.Events, d) 385 } 386 } 387 388 func appendMarkers(datadogGraph *datadog.Graph, terraformMarkers *[]interface{}) { 389 for _, t_ := range *terraformMarkers { 390 t := t_.(map[string]interface{}) 391 d := datadog.GraphDefinitionMarker{ 392 Type: t["type"].(string), 393 Value: t["value"].(string), 394 } 395 if label, ok := t["label"]; ok { 396 d.Label = label.(string) 397 } 398 datadogGraph.Definition.Markers = append(datadogGraph.Definition.Markers, d) 399 } 400 } 401 402 func buildGraphs(terraformGraphs *[]interface{}) *[]datadog.Graph { 403 datadogGraphs := make([]datadog.Graph, len(*terraformGraphs)) 404 for i, t_ := range *terraformGraphs { 405 t := t_.(map[string]interface{}) 406 datadogGraphs[i] = datadog.Graph{Title: t["title"].(string)} 407 d := &datadogGraphs[i] 408 d.Definition.Viz = t["viz"].(string) 409 410 if yaxis_, ok := t["yaxis"]; ok { 411 yaxis := yaxis_.(map[string]interface{}) 412 if min_, ok := yaxis["min"]; ok { 413 min, _ := strconv.ParseFloat(min_.(string), 64) 414 d.Definition.Yaxis.Min = &min 415 } 416 if max_, ok := yaxis["max"]; ok { 417 max, _ := strconv.ParseFloat(max_.(string), 64) 418 d.Definition.Yaxis.Max = &max 419 } 420 if scale_, ok := yaxis["scale"]; ok { 421 scale := scale_.(string) 422 d.Definition.Yaxis.Scale = &scale 423 } 424 } 425 426 if autoscale, ok := t["autoscale"]; ok { 427 d.Definition.Autoscale = autoscale.(bool) 428 } 429 430 if textAlign, ok := t["text_align"]; ok { 431 d.Definition.TextAlign = textAlign.(string) 432 } 433 434 if precision, ok := t["precision"]; ok { 435 d.Definition.Precision = precision.(string) 436 } 437 438 if customUnit, ok := t["custom_unit"]; ok { 439 d.Definition.CustomUnit = customUnit.(string) 440 } 441 442 if style, ok := t["style"]; ok { 443 s := style.(map[string]interface{}) 444 445 style := struct { 446 Palette *string `json:"palette,omitempty"` 447 PaletteFlip *bool `json:"paletteFlip,omitempty"` 448 }{} 449 450 if palette_, ok := s["palette"]; ok { 451 palette := palette_.(string) 452 style.Palette = &palette 453 } 454 455 if paletteFlip_, ok := s["palette_flip"]; ok { 456 paletteFlip, _ := strconv.ParseBool(paletteFlip_.(string)) 457 style.PaletteFlip = &paletteFlip 458 } 459 d.Definition.Style = &style 460 461 } 462 463 if groups, ok := t["group"]; ok { 464 for _, g := range groups.(*schema.Set).List() { 465 d.Definition.Groups = append(d.Definition.Groups, g.(string)) 466 } 467 } 468 469 if includeNoMetricHosts, ok := t["include_no_metric_hosts"]; ok { 470 d.Definition.IncludeNoMetricHosts = includeNoMetricHosts.(bool) 471 } 472 473 if scopes, ok := t["scope"]; ok { 474 for _, s := range scopes.(*schema.Set).List() { 475 d.Definition.Scopes = append(d.Definition.Groups, s.(string)) 476 } 477 } 478 479 if includeUngroupedHosts, ok := t["include_ungrouped_hosts"]; ok { 480 d.Definition.IncludeUngroupedHosts = includeUngroupedHosts.(bool) 481 } 482 terraformMarkers := t["marker"].([]interface{}) 483 appendMarkers(d, &terraformMarkers) 484 485 terraformEvents := t["events"].(*schema.Set).List() 486 appendEvents(d, &terraformEvents) 487 488 terraformRequests := t["request"].([]interface{}) 489 appendRequests(d, &terraformRequests) 490 } 491 return &datadogGraphs 492 } 493 494 func buildTimeboard(d *schema.ResourceData) (*datadog.Dashboard, error) { 495 var id int 496 if d.Id() != "" { 497 var err error 498 id, err = strconv.Atoi(d.Id()) 499 if err != nil { 500 return nil, err 501 } 502 } 503 terraformGraphs := d.Get("graph").([]interface{}) 504 terraformTemplateVariables := d.Get("template_variable").([]interface{}) 505 return &datadog.Dashboard{ 506 Id: id, 507 Title: d.Get("title").(string), 508 Description: d.Get("description").(string), 509 ReadOnly: d.Get("read_only").(bool), 510 Graphs: *buildGraphs(&terraformGraphs), 511 TemplateVariables: *buildTemplateVariables(&terraformTemplateVariables), 512 }, nil 513 } 514 515 func resourceDatadogTimeboardCreate(d *schema.ResourceData, meta interface{}) error { 516 timeboard, err := buildTimeboard(d) 517 if err != nil { 518 return fmt.Errorf("Failed to parse resource configuration: %s", err.Error()) 519 } 520 timeboard, err = meta.(*datadog.Client).CreateDashboard(timeboard) 521 if err != nil { 522 return fmt.Errorf("Failed to create timeboard using Datadog API: %s", err.Error()) 523 } 524 d.SetId(strconv.Itoa(timeboard.Id)) 525 return nil 526 } 527 528 func resourceDatadogTimeboardUpdate(d *schema.ResourceData, meta interface{}) error { 529 timeboard, err := buildTimeboard(d) 530 if err != nil { 531 return fmt.Errorf("Failed to parse resource configuration: %s", err.Error()) 532 } 533 if err = meta.(*datadog.Client).UpdateDashboard(timeboard); err != nil { 534 return fmt.Errorf("Failed to update timeboard using Datadog API: %s", err.Error()) 535 } 536 return resourceDatadogTimeboardRead(d, meta) 537 } 538 539 func appendTerraformGraphRequests(datadogRequests []datadog.GraphDefinitionRequest, requests *[]map[string]interface{}) { 540 for _, datadogRequest := range datadogRequests { 541 request := map[string]interface{}{} 542 request["q"] = datadogRequest.Query 543 request["stacked"] = datadogRequest.Stacked 544 request["type"] = datadogRequest.Type 545 if datadogRequest.Style != nil { 546 style := map[string]string{} 547 if datadogRequest.Style.Palette != nil { 548 style["palette"] = *datadogRequest.Style.Palette 549 } 550 if datadogRequest.Style.Type != nil { 551 style["type"] = *datadogRequest.Style.Type 552 } 553 if datadogRequest.Style.Width != nil { 554 style["width"] = *datadogRequest.Style.Width 555 } 556 request["style"] = style 557 } 558 conditionalFormats := []map[string]interface{}{} 559 for _, cf := range datadogRequest.ConditionalFormats { 560 conditionalFormat := map[string]interface{}{ 561 "palette": cf.Palette, 562 "comparator": cf.Comparator, 563 "custom_bg_color": cf.CustomBgColor, 564 "value": cf.Value, 565 "custom_fg_color": cf.CustomFgColor, 566 } 567 conditionalFormats = append(conditionalFormats, conditionalFormat) 568 } 569 request["conditional_format"] = conditionalFormats 570 request["change_type"] = datadogRequest.ChangeType 571 request["order_direction"] = datadogRequest.OrderDirection 572 request["compare_to"] = datadogRequest.CompareTo 573 request["increase_good"] = datadogRequest.IncreaseGood 574 request["order_by"] = datadogRequest.OrderBy 575 request["extra_col"] = datadogRequest.ExtraCol 576 577 *requests = append(*requests, request) 578 } 579 } 580 581 func buildTerraformGraph(datadog_graph datadog.Graph) map[string]interface{} { 582 graph := map[string]interface{}{} 583 graph["title"] = datadog_graph.Title 584 585 definition := datadog_graph.Definition 586 graph["viz"] = definition.Viz 587 588 events := []string{} 589 for _, datadog_event := range definition.Events { 590 events = append(events, datadog_event.Query) 591 } 592 graph["events"] = events 593 594 markers := []map[string]interface{}{} 595 for _, datadog_marker := range definition.Markers { 596 marker := map[string]interface{}{ 597 "type": datadog_marker.Type, 598 "value": datadog_marker.Value, 599 "label": datadog_marker.Label, 600 } 601 markers = append(markers, marker) 602 } 603 graph["marker"] = markers 604 605 yaxis := map[string]string{} 606 607 if definition.Yaxis.Min != nil { 608 yaxis["min"] = strconv.FormatFloat(*definition.Yaxis.Min, 'f', -1, 64) 609 } 610 611 if definition.Yaxis.Max != nil { 612 yaxis["max"] = strconv.FormatFloat(*definition.Yaxis.Max, 'f', -1, 64) 613 } 614 615 if definition.Yaxis.Scale != nil { 616 yaxis["scale"] = *definition.Yaxis.Scale 617 } 618 619 graph["yaxis"] = yaxis 620 621 graph["autoscale"] = definition.Autoscale 622 graph["text_align"] = definition.TextAlign 623 graph["precision"] = definition.Precision 624 graph["custom_unit"] = definition.CustomUnit 625 626 if definition.Style != nil { 627 style := map[string]string{} 628 if definition.Style.Palette != nil { 629 style["palette"] = *definition.Style.Palette 630 } 631 if definition.Style.PaletteFlip != nil { 632 style["palette_flip"] = strconv.FormatBool(*definition.Style.PaletteFlip) 633 } 634 graph["style"] = style 635 } 636 graph["group"] = definition.Groups 637 graph["include_no_metric_hosts"] = definition.IncludeNoMetricHosts 638 graph["scope"] = definition.Scopes 639 graph["include_ungrouped_hosts"] = definition.IncludeUngroupedHosts 640 641 requests := []map[string]interface{}{} 642 appendTerraformGraphRequests(definition.Requests, &requests) 643 graph["request"] = requests 644 645 return graph 646 } 647 648 func resourceDatadogTimeboardRead(d *schema.ResourceData, meta interface{}) error { 649 id, err := strconv.Atoi(d.Id()) 650 if err != nil { 651 return err 652 } 653 timeboard, err := meta.(*datadog.Client).GetDashboard(id) 654 if err != nil { 655 return err 656 } 657 log.Printf("[DEBUG] timeboard: %v", timeboard) 658 d.Set("title", timeboard.Title) 659 d.Set("description", timeboard.Description) 660 661 graphs := []map[string]interface{}{} 662 for _, datadog_graph := range timeboard.Graphs { 663 graphs = append(graphs, buildTerraformGraph(datadog_graph)) 664 } 665 d.Set("graph", graphs) 666 667 templateVariables := []map[string]string{} 668 for _, templateVariable := range timeboard.TemplateVariables { 669 tv := map[string]string{ 670 "name": templateVariable.Name, 671 "prefix": templateVariable.Prefix, 672 "default": templateVariable.Default, 673 } 674 templateVariables = append(templateVariables, tv) 675 } 676 d.Set("template_variable", templateVariables) 677 678 return nil 679 } 680 681 func resourceDatadogTimeboardDelete(d *schema.ResourceData, meta interface{}) error { 682 id, err := strconv.Atoi(d.Id()) 683 if err != nil { 684 return err 685 } 686 if err = meta.(*datadog.Client).DeleteDashboard(id); err != nil { 687 return err 688 } 689 return nil 690 } 691 692 func resourceDatadogTimeboardImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 693 if err := resourceDatadogTimeboardRead(d, meta); err != nil { 694 return nil, err 695 } 696 return []*schema.ResourceData{d}, nil 697 } 698 699 func resourceDatadogTimeboardExists(d *schema.ResourceData, meta interface{}) (b bool, e error) { 700 id, err := strconv.Atoi(d.Id()) 701 if err != nil { 702 return false, err 703 } 704 if _, err = meta.(*datadog.Client).GetDashboard(id); err != nil { 705 if strings.Contains(err.Error(), "404 Not Found") { 706 return false, nil 707 } 708 return false, err 709 } 710 return true, nil 711 } 712 713 func validateAggregatorMethod(v interface{}, k string) (ws []string, errors []error) { 714 value := v.(string) 715 validMethods := map[string]struct{}{ 716 "average": {}, 717 "max": {}, 718 "min": {}, 719 "sum": {}, 720 } 721 if _, ok := validMethods[value]; !ok { 722 errors = append(errors, fmt.Errorf( 723 `%q contains an invalid method %q. Valid methods are either "average", "max", "min", or "sum"`, k, value)) 724 } 725 return 726 }