github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 "gopkg.in/zorkian/go-datadog-api.v2" 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: datadog.String(t["comparator"].(string)), 277 } 278 279 if v, ok := t["palette"]; ok { 280 d.SetPalette(v.(string)) 281 } 282 283 if v, ok := t["custom_bg_color"]; ok { 284 d.SetCustomBgColor(v.(string)) 285 } 286 287 if v, ok := t["custom_fg_color"]; ok { 288 d.SetCustomFgColor(v.(string)) 289 } 290 291 if v, ok := t["value"]; ok { 292 d.SetValue(json.Number(v.(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: datadog.String(t["name"].(string)), 305 Prefix: datadog.String(t["prefix"].(string)), 306 Default: datadog.String(t["default"].(string)), 307 } 308 } 309 return &datadogTemplateVariables 310 } 311 312 func appendRequests(datadogGraph *datadog.Graph, terraformRequests *[]interface{}) { 313 for _, t_ := range *terraformRequests { 314 t := t_.(map[string]interface{}) 315 d := datadog.GraphDefinitionRequest{ 316 Query: datadog.String(t["q"].(string)), 317 Type: datadog.String(t["type"].(string)), 318 Aggregator: datadog.String(t["aggregator"].(string)), 319 } 320 if stacked, ok := t["stacked"]; ok { 321 d.SetStacked(stacked.(bool)) 322 } 323 if style, ok := t["style"]; ok { 324 s, _ := style.(map[string]interface{}) 325 326 style := datadog.GraphDefinitionRequestStyle{} 327 328 if v, ok := s["palette"]; ok { 329 style.SetPalette(v.(string)) 330 } 331 332 if v, ok := s["width"]; ok { 333 style.SetWidth(v.(string)) 334 } 335 336 if v, ok := s["type"]; ok { 337 style.SetType(v.(string)) 338 } 339 340 d.SetStyle(style) 341 } 342 343 if v, ok := t["change_type"]; ok { 344 d.SetChangeType(v.(string)) 345 } 346 if v, ok := t["compare_to"]; ok { 347 d.SetCompareTo(v.(string)) 348 } 349 if v, ok := t["increase_good"]; ok { 350 d.SetIncreaseGood(v.(bool)) 351 } 352 if v, ok := t["order_by"]; ok { 353 d.SetOrderBy(v.(string)) 354 } 355 if v, ok := t["extra_col"]; ok { 356 d.SetExtraCol(v.(string)) 357 } 358 if v, ok := t["order_direction"]; ok { 359 d.SetOrderDirection(v.(string)) 360 } 361 362 if v, ok := t["conditional_format"]; ok { 363 v_ := v.([]interface{}) 364 appendConditionalFormats(&d, &v_) 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 datadogGraph.Definition.Events = append(datadogGraph.Definition.Events, datadog.GraphEvent{ 374 Query: datadog.String(t_.(string)), 375 }) 376 } 377 } 378 379 func appendMarkers(datadogGraph *datadog.Graph, terraformMarkers *[]interface{}) { 380 for _, t_ := range *terraformMarkers { 381 t := t_.(map[string]interface{}) 382 d := datadog.GraphDefinitionMarker{ 383 Type: datadog.String(t["type"].(string)), 384 Value: datadog.String(t["value"].(string)), 385 } 386 if v, ok := t["label"]; ok { 387 d.SetLabel(v.(string)) 388 } 389 datadogGraph.Definition.Markers = append(datadogGraph.Definition.Markers, d) 390 } 391 } 392 393 func buildGraphs(terraformGraphs *[]interface{}) *[]datadog.Graph { 394 datadogGraphs := make([]datadog.Graph, len(*terraformGraphs)) 395 for i, t_ := range *terraformGraphs { 396 t := t_.(map[string]interface{}) 397 398 datadogGraphs[i] = datadog.Graph{ 399 Title: datadog.String(t["title"].(string)), 400 } 401 402 d := &datadogGraphs[i] 403 d.Definition = &datadog.GraphDefinition{} 404 d.Definition.SetViz(t["viz"].(string)) 405 406 if v, ok := t["yaxis"]; ok { 407 yaxis := v.(map[string]interface{}) 408 if v, ok := yaxis["min"]; ok { 409 min, _ := strconv.ParseFloat(v.(string), 64) 410 d.Definition.Yaxis.SetMin(min) 411 } 412 if v, ok := yaxis["max"]; ok { 413 max, _ := strconv.ParseFloat(v.(string), 64) 414 d.Definition.Yaxis.SetMax(max) 415 } 416 if v, ok := yaxis["scale"]; ok { 417 d.Definition.Yaxis.SetScale(v.(string)) 418 } 419 } 420 421 if v, ok := t["autoscale"]; ok { 422 d.Definition.SetAutoscale(v.(bool)) 423 } 424 425 if v, ok := t["text_align"]; ok { 426 d.Definition.SetTextAlign(v.(string)) 427 } 428 429 if precision, ok := t["precision"]; ok { 430 d.Definition.SetPrecision(precision.(string)) 431 } 432 433 if v, ok := t["custom_unit"]; ok { 434 d.Definition.SetCustomUnit(v.(string)) 435 } 436 437 if style, ok := t["style"]; ok { 438 s := style.(map[string]interface{}) 439 440 gs := datadog.Style{} 441 442 if v, ok := s["palette"]; ok { 443 gs.SetPalette(v.(string)) 444 } 445 446 if v, ok := s["palette_flip"]; ok { 447 pf, _ := strconv.ParseBool(v.(string)) 448 gs.SetPaletteFlip(pf) 449 } 450 d.Definition.SetStyle(gs) 451 452 } 453 454 if v, ok := t["group"]; ok { 455 for _, g := range v.(*schema.Set).List() { 456 d.Definition.Groups = append(d.Definition.Groups, g.(string)) 457 } 458 } 459 460 if includeNoMetricHosts, ok := t["include_no_metric_hosts"]; ok { 461 d.Definition.SetIncludeNoMetricHosts(includeNoMetricHosts.(bool)) 462 } 463 464 if v, ok := t["scope"]; ok { 465 for _, s := range v.(*schema.Set).List() { 466 d.Definition.Scopes = append(d.Definition.Groups, s.(string)) 467 } 468 } 469 470 if v, ok := t["include_ungrouped_hosts"]; ok { 471 d.Definition.SetIncludeUngroupedHosts(v.(bool)) 472 } 473 v := t["marker"].([]interface{}) 474 appendMarkers(d, &v) 475 476 v = t["events"].(*schema.Set).List() 477 appendEvents(d, &v) 478 479 v = t["request"].([]interface{}) 480 appendRequests(d, &v) 481 } 482 return &datadogGraphs 483 } 484 485 func buildTimeboard(d *schema.ResourceData) (*datadog.Dashboard, error) { 486 var id int 487 if d.Id() != "" { 488 var err error 489 id, err = strconv.Atoi(d.Id()) 490 if err != nil { 491 return nil, err 492 } 493 } 494 terraformGraphs := d.Get("graph").([]interface{}) 495 terraformTemplateVariables := d.Get("template_variable").([]interface{}) 496 return &datadog.Dashboard{ 497 Id: datadog.Int(id), 498 Title: datadog.String(d.Get("title").(string)), 499 Description: datadog.String(d.Get("description").(string)), 500 ReadOnly: datadog.Bool(d.Get("read_only").(bool)), 501 Graphs: *buildGraphs(&terraformGraphs), 502 TemplateVariables: *buildTemplateVariables(&terraformTemplateVariables), 503 }, nil 504 } 505 506 func resourceDatadogTimeboardCreate(d *schema.ResourceData, meta interface{}) error { 507 timeboard, err := buildTimeboard(d) 508 if err != nil { 509 return fmt.Errorf("Failed to parse resource configuration: %s", err.Error()) 510 } 511 timeboard, err = meta.(*datadog.Client).CreateDashboard(timeboard) 512 if err != nil { 513 return fmt.Errorf("Failed to create timeboard using Datadog API: %s", err.Error()) 514 } 515 d.SetId(strconv.Itoa(timeboard.GetId())) 516 return nil 517 } 518 519 func resourceDatadogTimeboardUpdate(d *schema.ResourceData, meta interface{}) error { 520 timeboard, err := buildTimeboard(d) 521 if err != nil { 522 return fmt.Errorf("Failed to parse resource configuration: %s", err.Error()) 523 } 524 if err = meta.(*datadog.Client).UpdateDashboard(timeboard); err != nil { 525 return fmt.Errorf("Failed to update timeboard using Datadog API: %s", err.Error()) 526 } 527 return resourceDatadogTimeboardRead(d, meta) 528 } 529 530 func appendTerraformGraphRequests(datadogRequests []datadog.GraphDefinitionRequest, requests *[]map[string]interface{}) { 531 for _, datadogRequest := range datadogRequests { 532 request := map[string]interface{}{} 533 request["q"] = datadogRequest.GetQuery() 534 request["stacked"] = datadogRequest.GetStacked() 535 request["type"] = datadogRequest.GetType() 536 if v, ok := datadogRequest.GetStyleOk(); ok { 537 style := map[string]string{} 538 if v, ok := v.GetPaletteOk(); ok { 539 style["palette"] = v 540 } 541 if v, ok := v.GetTypeOk(); ok { 542 style["type"] = v 543 } 544 if v, ok := v.GetWidthOk(); ok { 545 style["width"] = v 546 } 547 request["style"] = style 548 } 549 conditionalFormats := []map[string]interface{}{} 550 for _, cf := range datadogRequest.ConditionalFormats { 551 conditionalFormat := map[string]interface{}{ 552 "palette": cf.Palette, 553 "comparator": cf.Comparator, 554 "custom_bg_color": cf.CustomBgColor, 555 "value": cf.Value, 556 "custom_fg_color": cf.CustomFgColor, 557 } 558 conditionalFormats = append(conditionalFormats, conditionalFormat) 559 } 560 request["conditional_format"] = conditionalFormats 561 request["change_type"] = datadogRequest.GetChangeType() 562 request["order_direction"] = datadogRequest.GetOrderDirection() 563 request["compare_to"] = datadogRequest.GetCompareTo() 564 request["increase_good"] = datadogRequest.GetIncreaseGood() 565 request["order_by"] = datadogRequest.GetOrderBy() 566 request["extra_col"] = datadogRequest.GetExtraCol() 567 568 *requests = append(*requests, request) 569 } 570 } 571 572 func buildTerraformGraph(datadog_graph datadog.Graph) map[string]interface{} { 573 graph := map[string]interface{}{} 574 graph["title"] = datadog_graph.GetTitle() 575 576 definition := datadog_graph.Definition 577 graph["viz"] = definition.GetViz() 578 579 events := []*string{} 580 for _, datadog_event := range definition.Events { 581 events = append(events, datadog_event.Query) 582 } 583 graph["events"] = events 584 585 markers := []map[string]interface{}{} 586 for _, datadog_marker := range definition.Markers { 587 marker := map[string]interface{}{ 588 "type": datadog_marker.Type, 589 "value": datadog_marker.Value, 590 "label": datadog_marker.Label, 591 } 592 markers = append(markers, marker) 593 } 594 graph["marker"] = markers 595 596 yaxis := map[string]string{} 597 598 if v, ok := definition.Yaxis.GetMinOk(); ok { 599 yaxis["min"] = strconv.FormatFloat(v, 'f', -1, 64) 600 } 601 602 if v, ok := definition.Yaxis.GetMaxOk(); ok { 603 yaxis["max"] = strconv.FormatFloat(v, 'f', -1, 64) 604 } 605 606 if v, ok := definition.Yaxis.GetScaleOk(); ok { 607 yaxis["scale"] = v 608 } 609 610 graph["yaxis"] = yaxis 611 612 graph["autoscale"] = definition.Autoscale 613 graph["text_align"] = definition.TextAlign 614 graph["precision"] = definition.Precision 615 graph["custom_unit"] = definition.CustomUnit 616 617 if v, ok := definition.GetStyleOk(); ok { 618 style := map[string]string{} 619 if v, ok := v.GetPaletteOk(); ok { 620 style["palette"] = v 621 } 622 if v, ok := v.GetPaletteFlipOk(); ok { 623 style["palette_flip"] = strconv.FormatBool(v) 624 } 625 graph["style"] = style 626 } 627 graph["group"] = definition.Groups 628 graph["include_no_metric_hosts"] = definition.IncludeNoMetricHosts 629 graph["scope"] = definition.Scopes 630 graph["include_ungrouped_hosts"] = definition.IncludeUngroupedHosts 631 632 requests := []map[string]interface{}{} 633 appendTerraformGraphRequests(definition.Requests, &requests) 634 graph["request"] = requests 635 636 return graph 637 } 638 639 func resourceDatadogTimeboardRead(d *schema.ResourceData, meta interface{}) error { 640 id, err := strconv.Atoi(d.Id()) 641 if err != nil { 642 return err 643 } 644 timeboard, err := meta.(*datadog.Client).GetDashboard(id) 645 if err != nil { 646 return err 647 } 648 log.Printf("[DEBUG] timeboard: %v", timeboard) 649 d.Set("title", timeboard.GetTitle()) 650 d.Set("description", timeboard.GetDescription()) 651 652 graphs := []map[string]interface{}{} 653 for _, datadog_graph := range timeboard.Graphs { 654 graphs = append(graphs, buildTerraformGraph(datadog_graph)) 655 } 656 d.Set("graph", graphs) 657 658 templateVariables := []map[string]*string{} 659 for _, templateVariable := range timeboard.TemplateVariables { 660 tv := map[string]*string{ 661 "name": templateVariable.Name, 662 "prefix": templateVariable.Prefix, 663 "default": templateVariable.Default, 664 } 665 templateVariables = append(templateVariables, tv) 666 } 667 d.Set("template_variable", templateVariables) 668 669 return nil 670 } 671 672 func resourceDatadogTimeboardDelete(d *schema.ResourceData, meta interface{}) error { 673 id, err := strconv.Atoi(d.Id()) 674 if err != nil { 675 return err 676 } 677 if err = meta.(*datadog.Client).DeleteDashboard(id); err != nil { 678 return err 679 } 680 return nil 681 } 682 683 func resourceDatadogTimeboardImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 684 if err := resourceDatadogTimeboardRead(d, meta); err != nil { 685 return nil, err 686 } 687 return []*schema.ResourceData{d}, nil 688 } 689 690 func resourceDatadogTimeboardExists(d *schema.ResourceData, meta interface{}) (b bool, e error) { 691 id, err := strconv.Atoi(d.Id()) 692 if err != nil { 693 return false, err 694 } 695 if _, err = meta.(*datadog.Client).GetDashboard(id); err != nil { 696 if strings.Contains(err.Error(), "404 Not Found") { 697 return false, nil 698 } 699 return false, err 700 } 701 return true, nil 702 } 703 704 func validateAggregatorMethod(v interface{}, k string) (ws []string, errors []error) { 705 value := v.(string) 706 validMethods := map[string]struct{}{ 707 "average": {}, 708 "max": {}, 709 "min": {}, 710 "sum": {}, 711 } 712 if _, ok := validMethods[value]; !ok { 713 errors = append(errors, fmt.Errorf( 714 `%q contains an invalid method %q. Valid methods are either "average", "max", "min", or "sum"`, k, value)) 715 } 716 return 717 }