Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/datadog: add a timeboard resource #6900

Merged
merged 1 commit into from
Jun 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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