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  }