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