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  }