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  }