Skip to content

Commit

Permalink
provider/datadog: add timeboard resource. upgrade vendored go-datadog…
Browse files Browse the repository at this point in the history
…-api to support read-only option. (hashicorp#6900)
  • Loading branch information
Brian Edwards authored and stack72 committed Jun 15, 2016
1 parent 14ff584 commit d79879d
Show file tree
Hide file tree
Showing 14 changed files with 1,808 additions and 1 deletion.
3 changes: 2 additions & 1 deletion builtin/providers/datadog/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"datadog_monitor": resourceDatadogMonitor(),
"datadog_monitor": resourceDatadogMonitor(),
"datadog_timeboard": resourceDatadogTimeboard(),
},

ConfigureFunc: providerConfigure,
Expand Down
231 changes: 231 additions & 0 deletions builtin/providers/datadog/resource_datadog_timeboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package datadog

import (
"fmt"
"log"
"strconv"
"strings"

"github.com/hashicorp/terraform/helper/schema"
"github.com/zorkian/go-datadog-api"
)

func resourceDatadogTimeboard() *schema.Resource {

request := &schema.Schema{
Type: schema.TypeList,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"q": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"stacked": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
},
}

graph := &schema.Schema{
Type: schema.TypeList,
Required: true,
Description: "A list of graph definitions.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"title": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "The name of the graph.",
},
"viz": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"request": request,
},
},
}

template_variable := &schema.Schema{
Type: schema.TypeList,
Optional: true,
Description: "A list of template variables for using Dashboard templating.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "The name of the variable.",
},
"prefix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The tag prefix associated with the variable. Only tags with this prefix will appear in the variable dropdown.",
},
"default": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The default value for the template variable on dashboard load.",
},
},
},
}

return &schema.Resource{
Create: resourceDatadogTimeboardCreate,
Update: resourceDatadogTimeboardUpdate,
Read: resourceDatadogTimeboardRead,
Delete: resourceDatadogTimeboardDelete,
Exists: resourceDatadogTimeboardExists,

Schema: map[string]*schema.Schema{
"title": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "The name of the dashboard.",
},
"description": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "A description of the dashboard's content.",
},
"read_only": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"graph": graph,
"template_variable": template_variable,
},
}
}

func buildTemplateVariables(terraformTemplateVariables *[]interface{}) *[]datadog.TemplateVariable {
datadogTemplateVariables := make([]datadog.TemplateVariable, len(*terraformTemplateVariables))
for i, t_ := range *terraformTemplateVariables {
t := t_.(map[string]interface{})
datadogTemplateVariables[i] = datadog.TemplateVariable{
Name: t["name"].(string),
Prefix: t["prefix"].(string),
Default: t["default"].(string)}
}
return &datadogTemplateVariables
}

func appendRequests(datadogGraph *datadog.Graph, terraformRequests *[]interface{}) {
for _, t_ := range *terraformRequests {
t := t_.(map[string]interface{})
d := struct {
Query string `json:"q"`
Stacked bool `json:"stacked"`
}{Query: t["q"].(string)}
if stacked, ok := t["stacked"]; ok {
d.Stacked = stacked.(bool)
}
datadogGraph.Definition.Requests = append(datadogGraph.Definition.Requests, d)
}
}

func buildGraphs(terraformGraphs *[]interface{}) *[]datadog.Graph {
datadogGraphs := make([]datadog.Graph, len(*terraformGraphs))
for i, t_ := range *terraformGraphs {
t := t_.(map[string]interface{})
datadogGraphs[i] = datadog.Graph{Title: t["title"].(string)}
d := &datadogGraphs[i]
d.Definition.Viz = t["viz"].(string)
terraformRequests := t["request"].([]interface{})
appendRequests(d, &terraformRequests)
}
return &datadogGraphs
}

func buildTimeboard(d *schema.ResourceData) (*datadog.Dashboard, error) {
var id int
if d.Id() != "" {
var err error
id, err = strconv.Atoi(d.Id())
if err != nil {
return nil, err
}
}
terraformGraphs := d.Get("graph").([]interface{})
terraformTemplateVariables := d.Get("template_variable").([]interface{})
return &datadog.Dashboard{
Id: id,
Title: d.Get("title").(string),
Description: d.Get("description").(string),
ReadOnly: d.Get("read_only").(bool),
Graphs: *buildGraphs(&terraformGraphs),
TemplateVariables: *buildTemplateVariables(&terraformTemplateVariables),
}, nil
}

func resourceDatadogTimeboardCreate(d *schema.ResourceData, meta interface{}) error {
timeboard, err := buildTimeboard(d)
if err != nil {
return fmt.Errorf("Failed to parse resource configuration: %s", err.Error())
}
timeboard, err = meta.(*datadog.Client).CreateDashboard(timeboard)
if err != nil {
return fmt.Errorf("Failed to create timeboard using Datadog API: %s", err.Error())
}
d.SetId(strconv.Itoa(timeboard.Id))
return nil
}

func resourceDatadogTimeboardUpdate(d *schema.ResourceData, meta interface{}) error {
timeboard, err := buildTimeboard(d)
if err != nil {
return fmt.Errorf("Failed to parse resource configuration: %s", err.Error())
}
if err = meta.(*datadog.Client).UpdateDashboard(timeboard); err != nil {
return fmt.Errorf("Failed to update timeboard using Datadog API: %s", err.Error())
}
return resourceDatadogTimeboardRead(d, meta)
}

func resourceDatadogTimeboardRead(d *schema.ResourceData, meta interface{}) error {
id, err := strconv.Atoi(d.Id())
if err != nil {
return err
}
timeboard, err := meta.(*datadog.Client).GetDashboard(id)
if err != nil {
return err
}
log.Printf("[DEBUG] timeboard: %v", timeboard)
d.Set("title", timeboard.Title)
d.Set("description", timeboard.Description)
d.Set("graphs", timeboard.Graphs)
d.Set("template_variables", timeboard.TemplateVariables)
return nil
}

func resourceDatadogTimeboardDelete(d *schema.ResourceData, meta interface{}) error {
id, err := strconv.Atoi(d.Id())
if err != nil {
return err
}
if err = meta.(*datadog.Client).DeleteDashboard(id); err != nil {
return err
}
return nil
}

func resourceDatadogTimeboardExists(d *schema.ResourceData, meta interface{}) (b bool, e error) {
id, err := strconv.Atoi(d.Id())
if err != nil {
return false, err
}
if _, err = meta.(*datadog.Client).GetDashboard(id); err != nil {
if strings.Contains(err.Error(), "404 Not Found") {
return false, nil
}
return false, err
}
return true, nil
}
124 changes: 124 additions & 0 deletions builtin/providers/datadog/resource_datadog_timeboard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package datadog

import (
"fmt"
"strconv"
"strings"
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/zorkian/go-datadog-api"
)

const config1 = `
resource "datadog_timeboard" "acceptance_test" {
title = "Acceptance Test Timeboard"
description = "Created using the Datadog prodivider in Terraform"
read_only = true
graph {
title = "Top System CPU by Docker container"
viz = "toplist"
request {
q = "top(avg:docker.cpu.system{*} by {container_name}, 10, 'mean', 'desc')"
}
}
}
`

const config2 = `
resource "datadog_timeboard" "acceptance_test" {
title = "Acceptance Test Timeboard"
description = "Created using the Datadog prodivider in Terraform"
graph {
title = "Redis latency (ms)"
viz = "timeseries"
request {
q = "avg:redis.info.latency_ms{$host}"
}
}
graph {
title = "Redis memory usage"
viz = "timeseries"
request {
q = "avg:redis.mem.used{$host} - avg:redis.mem.lua{$host}, avg:redis.mem.lua{$host}"
stacked = true
}
request {
q = "avg:redis.mem.rss{$host}"
}
}
template_variable {
name = "host"
prefix = "host"
}
}
`

func TestAccDatadogTimeboard_update(t *testing.T) {

step1 := resource.TestStep{
Config: config1,
Check: resource.ComposeTestCheckFunc(
checkExists,
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "title", "Acceptance Test Timeboard"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "description", "Created using the Datadog prodivider in Terraform"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "read_only", "true"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "graph.0.title", "Top System CPU by Docker container"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "graph.0.viz", "toplist"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "graph.0.request.0.q", "top(avg:docker.cpu.system{*} by {container_name}, 10, 'mean', 'desc')"),
),
}

step2 := resource.TestStep{
Config: config2,
Check: resource.ComposeTestCheckFunc(
checkExists,
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "title", "Acceptance Test Timeboard"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "description", "Created using the Datadog prodivider in Terraform"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "graph.0.title", "Redis latency (ms)"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "graph.0.viz", "timeseries"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "graph.0.request.0.q", "avg:redis.info.latency_ms{$host}"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "graph.1.title", "Redis memory usage"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "graph.1.viz", "timeseries"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "graph.1.request.0.q", "avg:redis.mem.used{$host} - avg:redis.mem.lua{$host}, avg:redis.mem.lua{$host}"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "graph.1.request.0.stacked", "true"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "graph.1.request.1.q", "avg:redis.mem.rss{$host}"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "template_variable.0.name", "host"),
resource.TestCheckResourceAttr("datadog_timeboard.acceptance_test", "template_variable.0.prefix", "host"),
),
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: checkDestroy,
Steps: []resource.TestStep{step1, step2},
})
}

func checkExists(s *terraform.State) error {
client := testAccProvider.Meta().(*datadog.Client)
for _, r := range s.RootModule().Resources {
i, _ := strconv.Atoi(r.Primary.ID)
if _, err := client.GetDashboard(i); err != nil {
return fmt.Errorf("Received an error retrieving monitor %s", err)
}
}
return nil
}

func checkDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*datadog.Client)
for _, r := range s.RootModule().Resources {
i, _ := strconv.Atoi(r.Primary.ID)
if _, err := client.GetDashboard(i); err != nil {
if strings.Contains(err.Error(), "404 Not Found") {
continue
}
return fmt.Errorf("Received an error retrieving timeboard %s", err)
}
return fmt.Errorf("Timeboard still exists")
}
return nil
}
22 changes: 22 additions & 0 deletions vendor/github.com/zorkian/go-datadog-api/checks_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions vendor/github.com/zorkian/go-datadog-api/dashboards.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit d79879d

Please sign in to comment.