forked from hashicorp/terraform
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
provider/datadog: add timeboard resource. upgrade vendored go-datadog…
…-api to support read-only option. (hashicorp#6900)
- Loading branch information
Showing
14 changed files
with
1,808 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
231 changes: 231 additions & 0 deletions
231
builtin/providers/datadog/resource_datadog_timeboard.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
124
builtin/providers/datadog/resource_datadog_timeboard_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.