diff --git a/builtin/providers/datadog/provider.go b/builtin/providers/datadog/provider.go index 7221f8481..a14bf07dd 100644 --- a/builtin/providers/datadog/provider.go +++ b/builtin/providers/datadog/provider.go @@ -23,7 +23,8 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "datadog_monitor": resourceDatadogMonitor(), + "datadog_monitor": resourceDatadogMonitor(), + "datadog_timeboard": resourceDatadogTimeboard(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/datadog/resource_datadog_timeboard.go b/builtin/providers/datadog/resource_datadog_timeboard.go new file mode 100644 index 000000000..9bd195fde --- /dev/null +++ b/builtin/providers/datadog/resource_datadog_timeboard.go @@ -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 +} diff --git a/builtin/providers/datadog/resource_datadog_timeboard_test.go b/builtin/providers/datadog/resource_datadog_timeboard_test.go new file mode 100644 index 000000000..e64de47cb --- /dev/null +++ b/builtin/providers/datadog/resource_datadog_timeboard_test.go @@ -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 +} diff --git a/vendor/github.com/zorkian/go-datadog-api/checks_test.go b/vendor/github.com/zorkian/go-datadog-api/checks_test.go new file mode 100644 index 000000000..135ee5def --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/checks_test.go @@ -0,0 +1,22 @@ +package datadog_test + +import ( + "testing" + + "github.com/zorkian/go-datadog-api" +) + +func TestCheckStatus(T *testing.T) { + if datadog.OK != 0 { + T.Error("status OK must be 0 to satisfy Datadog's API") + } + if datadog.WARNING != 1 { + T.Error("status WARNING must be 1 to satisfy Datadog's API") + } + if datadog.CRITICAL != 2 { + T.Error("status CRITICAL must be 2 to satisfy Datadog's API") + } + if datadog.UNKNOWN != 3 { + T.Error("status UNKNOWN must be 3 to satisfy Datadog's API") + } +} diff --git a/vendor/github.com/zorkian/go-datadog-api/dashboards.go b/vendor/github.com/zorkian/go-datadog-api/dashboards.go index c21a34ed4..9c4be48b0 100644 --- a/vendor/github.com/zorkian/go-datadog-api/dashboards.go +++ b/vendor/github.com/zorkian/go-datadog-api/dashboards.go @@ -40,6 +40,7 @@ type Dashboard struct { Title string `json:"title"` Graphs []Graph `json:"graphs"` TemplateVariables []TemplateVariable `json:"template_variables,omitempty"` + ReadOnly bool `json:"read_only"` } // DashboardLite represents a user created dashboard. This is the mini diff --git a/vendor/github.com/zorkian/go-datadog-api/integration/dashboards_test.go b/vendor/github.com/zorkian/go-datadog-api/integration/dashboards_test.go new file mode 100644 index 000000000..272a5e161 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/integration/dashboards_test.go @@ -0,0 +1,138 @@ +package integration + +import ( + "github.com/zorkian/go-datadog-api" + "testing" +) + +func init() { + client = initTest() +} + +func TestCreateAndDeleteDashboard(t *testing.T) { + expected := getTestDashboard() + // create the dashboard and compare it + actual, err := client.CreateDashboard(expected) + if err != nil { + t.Fatalf("Creating a dashboard failed when it shouldn't. (%s)", err) + } + + defer cleanUpDashboard(t, actual.Id) + + assertDashboardEquals(t, actual, expected) + + // now try to fetch it freshly and compare it again + actual, err = client.GetDashboard(actual.Id) + if err != nil { + t.Fatalf("Retrieving a dashboard failed when it shouldn't. (%s)", err) + } + assertDashboardEquals(t, actual, expected) + +} + +func TestUpdateDashboard(t *testing.T) { + expected := getTestDashboard() + board, err := client.CreateDashboard(expected) + if err != nil { + t.Fatalf("Creating a dashboard failed when it shouldn't. (%s)", err) + } + + defer cleanUpDashboard(t, board.Id) + board.Title = "___New-Test-Board___" + + if err := client.UpdateDashboard(board); err != nil { + t.Fatalf("Updating a dashboard failed when it shouldn't: %s", err) + } + + actual, err := client.GetDashboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a dashboard failed when it shouldn't: %s", err) + } + + assertDashboardEquals(t, actual, board) +} + +func TestGetDashboards(t *testing.T) { + boards, err := client.GetDashboards() + if err != nil { + t.Fatalf("Retrieving dashboards failed when it shouldn't: %s", err) + } + + num := len(boards) + board := createTestDashboard(t) + defer cleanUpDashboard(t, board.Id) + + boards, err = client.GetDashboards() + if err != nil { + t.Fatalf("Retrieving dashboards failed when it shouldn't: %s", err) + } + + if num+1 != len(boards) { + t.Fatalf("Number of dashboards didn't match expected: %d != %d", len(boards), num+1) + } +} + +func getTestDashboard() *datadog.Dashboard { + return &datadog.Dashboard{ + Title: "___Test-Board___", + Description: "Testboard description", + TemplateVariables: []datadog.TemplateVariable{}, + Graphs: createGraph(), + } +} + +func createTestDashboard(t *testing.T) *datadog.Dashboard { + board := getTestDashboard() + board, err := client.CreateDashboard(board) + if err != nil { + t.Fatalf("Creating a dashboard failed when it shouldn't: %s", err) + } + + return board +} + +func cleanUpDashboard(t *testing.T, id int) { + if err := client.DeleteDashboard(id); err != nil { + t.Fatalf("Deleting a dashboard failed when it shouldn't. Manual cleanup needed. (%s)", err) + } + + deletedBoard, err := client.GetDashboard(id) + if deletedBoard != nil { + t.Fatal("Dashboard hasn't been deleted when it should have been. Manual cleanup needed.") + } + + if err == nil { + t.Fatal("Fetching deleted dashboard didn't lead to an error. Manual cleanup needed.") + } +} + +type TestGraphDefintionRequests struct { + Query string `json:"q"` + Stacked bool `json:"stacked"` +} + +func createGraph() []datadog.Graph { + graphDefinition := datadog.Graph{}.Definition + graphDefinition.Viz = "timeseries" + r := datadog.Graph{}.Definition.Requests + graphDefinition.Requests = append(r, TestGraphDefintionRequests{Query: "avg:system.mem.free{*}", Stacked: false}) + graph := datadog.Graph{Title: "Mandatory graph", Definition: graphDefinition} + graphs := []datadog.Graph{} + graphs = append(graphs, graph) + return graphs +} + +func assertDashboardEquals(t *testing.T, actual, expected *datadog.Dashboard) { + if actual.Title != expected.Title { + t.Errorf("Dashboard title does not match: %s != %s", actual.Title, expected.Title) + } + if actual.Description != expected.Description { + t.Errorf("Dashboard description does not match: %s != %s", actual.Description, expected.Description) + } + if len(actual.Graphs) != len(expected.Graphs) { + t.Errorf("Number of Dashboard graphs does not match: %d != %d", len(actual.Graphs), len(expected.Graphs)) + } + if len(actual.TemplateVariables) != len(expected.TemplateVariables) { + t.Errorf("Number of Dashboard template variables does not match: %d != %d", len(actual.TemplateVariables), len(expected.TemplateVariables)) + } +} diff --git a/vendor/github.com/zorkian/go-datadog-api/integration/downtime_test.go b/vendor/github.com/zorkian/go-datadog-api/integration/downtime_test.go new file mode 100644 index 000000000..026cd5857 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/integration/downtime_test.go @@ -0,0 +1,110 @@ +package integration + +import ( + "github.com/stretchr/testify/assert" + "github.com/zorkian/go-datadog-api" + "testing" +) + +func init() { + client = initTest() +} + +func TestCreateAndDeleteDowntime(t *testing.T) { + expected := getTestDowntime() + // create the downtime and compare it + actual := createTestDowntime(t) + defer cleanUpDowntime(t, actual.Id) + + // Set ID of our original struct to zero we we can easily compare the results + expected.Id = actual.Id + assert.Equal(t, expected, actual) + + actual, err := client.GetDowntime(actual.Id) + if err != nil { + t.Fatalf("Retrieving a downtime failed when it shouldn't: (%s)", err) + } + assert.Equal(t, expected, actual) +} + +func TestUpdateDowntime(t *testing.T) { + + downtime := createTestDowntime(t) + + downtime.Scope = []string{"env:downtime_test", "env:downtime_test2"} + defer cleanUpDowntime(t, downtime.Id) + + if err := client.UpdateDowntime(downtime); err != nil { + t.Fatalf("Updating a downtime failed when it shouldn't: %s", err) + } + + actual, err := client.GetDowntime(downtime.Id) + if err != nil { + t.Fatalf("Retrieving a downtime failed when it shouldn't: %s", err) + } + + assert.Equal(t, downtime, actual) + +} + +func TestGetDowntime(t *testing.T) { + downtimes, err := client.GetDowntimes() + if err != nil { + t.Fatalf("Retrieving downtimes failed when it shouldn't: %s", err) + } + num := len(downtimes) + + downtime := createTestDowntime(t) + defer cleanUpDowntime(t, downtime.Id) + + downtimes, err = client.GetDowntimes() + if err != nil { + t.Fatalf("Retrieving downtimes failed when it shouldn't: %s", err) + } + + if num+1 != len(downtimes) { + t.Fatalf("Number of downtimes didn't match expected: %d != %d", len(downtimes), num+1) + } +} + +func getTestDowntime() *datadog.Downtime { + + r := &datadog.Recurrence{ + Type: "weeks", + Period: 1, + WeekDays: []string{"Mon", "Tue", "Wed", "Thu", "Fri"}, + } + + return &datadog.Downtime{ + Message: "Test downtime message", + Scope: []string{"env:downtime_test"}, + Start: 1577836800, + End: 1577840400, + Recurrence: r, + } +} + +func createTestDowntime(t *testing.T) *datadog.Downtime { + downtime := getTestDowntime() + downtime, err := client.CreateDowntime(downtime) + if err != nil { + t.Fatalf("Creating a downtime failed when it shouldn't: %s", err) + } + + return downtime +} + +func cleanUpDowntime(t *testing.T, id int) { + if err := client.DeleteDowntime(id); err != nil { + t.Fatalf("Deleting a downtime failed when it shouldn't. Manual cleanup needed. (%s)", err) + } + + deletedDowntime, err := client.GetDowntime(id) + if deletedDowntime != nil && deletedDowntime.Canceled == 0 { + t.Fatal("Downtime hasn't been deleted when it should have been. Manual cleanup needed.") + } + + if err == nil && deletedDowntime.Canceled == 0 { + t.Fatal("Fetching deleted downtime didn't lead to an error and downtime Canceled not set.") + } +} diff --git a/vendor/github.com/zorkian/go-datadog-api/integration/monitors_test.go b/vendor/github.com/zorkian/go-datadog-api/integration/monitors_test.go new file mode 100644 index 000000000..527fc63fa --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/integration/monitors_test.go @@ -0,0 +1,152 @@ +package integration + +import ( + "github.com/stretchr/testify/assert" + "github.com/zorkian/go-datadog-api" + "testing" +) + +func init() { + client = initTest() +} + +func TestCreateAndDeleteMonitor(t *testing.T) { + expected := getTestMonitor() + // create the monitor and compare it + actual := createTestMonitor(t) + defer cleanUpMonitor(t, actual.Id) + + // Set ID of our original struct to zero we we can easily compare the results + expected.Id = actual.Id + assert.Equal(t, expected, actual) + + actual, err := client.GetMonitor(actual.Id) + if err != nil { + t.Fatalf("Retrieving a monitor failed when it shouldn't: (%s)", err) + } + assert.Equal(t, expected, actual) +} + +func TestUpdateMonitor(t *testing.T) { + + monitor := createTestMonitor(t) + defer cleanUpMonitor(t, monitor.Id) + + monitor.Name = "___New-Test-Monitor___" + if err := client.UpdateMonitor(monitor); err != nil { + t.Fatalf("Updating a monitor failed when it shouldn't: %s", err) + } + + actual, err := client.GetMonitor(monitor.Id) + if err != nil { + t.Fatalf("Retrieving a monitor failed when it shouldn't: %s", err) + } + + assert.Equal(t, monitor, actual) + +} + +func TestGetMonitor(t *testing.T) { + monitors, err := client.GetMonitors() + if err != nil { + t.Fatalf("Retrieving monitors failed when it shouldn't: %s", err) + } + num := len(monitors) + + monitor := createTestMonitor(t) + defer cleanUpMonitor(t, monitor.Id) + + monitors, err = client.GetMonitors() + if err != nil { + t.Fatalf("Retrieving monitors failed when it shouldn't: %s", err) + } + + if num+1 != len(monitors) { + t.Fatalf("Number of monitors didn't match expected: %d != %d", len(monitors), num+1) + } +} + +func TestMuteUnmuteMonitor(t *testing.T) { + monitor := createTestMonitor(t) + defer cleanUpMonitor(t, monitor.Id) + + // Mute + err := client.MuteMonitor(monitor.Id) + if err != nil { + t.Fatalf("Failed to mute monitor") + + } + + monitor, err = client.GetMonitor(monitor.Id) + if err != nil { + t.Fatalf("Retrieving monitors failed when it shouldn't: %s", err) + } + + // Mute without options will result in monitor.Options.Silenced + // to have a key of "*" with value 0 + assert.Equal(t, 0, monitor.Options.Silenced["*"]) + + // Unmute + err = client.UnmuteMonitor(monitor.Id) + if err != nil { + t.Fatalf("Failed to unmute monitor") + } + + // Update remote state + monitor, err = client.GetMonitor(monitor.Id) + if err != nil { + t.Fatalf("Retrieving monitors failed when it shouldn't: %s", err) + } + + // Assert this map is empty + assert.Equal(t, 0, len(monitor.Options.Silenced)) +} + +/* + Testing of global mute and unmuting has not been added for following reasons: + * Disabling and enabling of global monitoring does an @all mention which is noisy + * It exposes risk to users that run integration tests in their main account + * There is no endpoint to verify success +*/ + +func getTestMonitor() *datadog.Monitor { + + o := datadog.Options{ + NotifyNoData: true, + NoDataTimeframe: 60, + Silenced: map[string]int{}, + } + + return &datadog.Monitor{ + Message: "Test message", + Query: "avg(last_15m):avg:system.disk.in_use{*} by {host,device} > 0.8", + Name: "Test monitor", + Options: o, + Type: "metric alert", + } +} + +func createTestMonitor(t *testing.T) *datadog.Monitor { + monitor := getTestMonitor() + monitor, err := client.CreateMonitor(monitor) + if err != nil { + t.Fatalf("Creating a monitor failed when it shouldn't: %s", err) + } + + return monitor +} + +func cleanUpMonitor(t *testing.T, id int) { + if err := client.DeleteMonitor(id); err != nil { + t.Fatalf("Deleting a monitor failed when it shouldn't. Manual cleanup needed. (%s)", err) + } + + deletedMonitor, err := client.GetMonitor(id) + if deletedMonitor != nil { + t.Fatal("Monitor hasn't been deleted when it should have been. Manual cleanup needed.") + } + + if err == nil { + t.Fatal("Fetching deleted monitor didn't lead to an error.") + } +} diff --git a/vendor/github.com/zorkian/go-datadog-api/integration/screen_widgets_test.go b/vendor/github.com/zorkian/go-datadog-api/integration/screen_widgets_test.go new file mode 100644 index 000000000..6611ab090 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/integration/screen_widgets_test.go @@ -0,0 +1,766 @@ +package integration + +import ( + "testing" + + "github.com/zorkian/go-datadog-api" +) + +func TestAlertValueWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.AlertValueWidget + + expected.X = 1 + expected.Y = 1 + expected.Width = 5 + expected.Height = 5 + expected.TitleText = "foo" + expected.TitleAlign = "center" + expected.TitleSize = 1 + expected.Title = true + expected.TextSize = "auto" + expected.Precision = 2 + expected.AlertId = 1 + expected.Type = "alert_value" + expected.Unit = "auto" + expected.AddTimeframe = false + + w := datadog.Widget{AlertValueWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].AlertValueWidget + + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "text_size", actualWidget.TextSize, expected.TextSize) + assertEquals(t, "precision", actualWidget.Precision, expected.Precision) + assertEquals(t, "alert_id", actualWidget.AlertId, expected.AlertId) + assertEquals(t, "type", actualWidget.Type, expected.Type) + assertEquals(t, "unit", actualWidget.Unit, expected.Unit) + assertEquals(t, "add_timeframe", actualWidget.AddTimeframe, expected.AddTimeframe) +} + +func TestChangeWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.ChangeWidget + + expected.X = 1 + expected.Y = 1 + expected.Width = 5 + expected.Height = 5 + expected.TitleText = "foo" + expected.TitleAlign = "center" + expected.TitleSize = 1 + expected.Title = true + expected.Aggregator = "min" + expected.TileDef = datadog.TileDef{} + + w := datadog.Widget{ChangeWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].ChangeWidget + + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "aggregator", actualWidget.Aggregator, expected.Aggregator) + assertTileDefEquals(t, actualWidget.TileDef, expected.TileDef) +} + +func TestGraphWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.GraphWidget + + expected.X = 1 + expected.Y = 1 + expected.Width = 5 + expected.Height = 5 + expected.TitleText = "foo" + expected.TitleAlign = "center" + expected.TitleSize = 1 + expected.Title = true + expected.Timeframe = "1d" + expected.Type = "alert_graph" + expected.Legend = true + expected.LegendSize = 5 + expected.TileDef = datadog.TileDef{} + + w := datadog.Widget{GraphWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].GraphWidget + + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "type", actualWidget.Type, expected.Type) + assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe) + assertEquals(t, "legend", actualWidget.Legend, expected.Legend) + assertEquals(t, "legend_size", actualWidget.LegendSize, expected.LegendSize) + assertTileDefEquals(t, actualWidget.TileDef, expected.TileDef) +} + +func TestEventTimelineWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.EventTimelineWidget + + expected.X = 1 + expected.Y = 1 + expected.Width = 5 + expected.Height = 5 + expected.TitleText = "foo" + expected.TitleAlign = "center" + expected.TitleSize = 1 + expected.Title = true + expected.Query = "avg:system.load.1{foo} by {bar}" + expected.Timeframe = "1d" + expected.Type = "alert_graph" + + w := datadog.Widget{EventTimelineWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].EventTimelineWidget + + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "type", actualWidget.Type, expected.Type) + assertEquals(t, "query", actualWidget.Query, expected.Query) + assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe) +} + +func TestAlertGraphWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.AlertGraphWidget + + expected.X = 1 + expected.Y = 1 + expected.Width = 5 + expected.Height = 5 + expected.TitleText = "foo" + expected.TitleAlign = "center" + expected.TitleSize = 1 + expected.Title = true + expected.VizType = "" + expected.Timeframe = "1d" + expected.AddTimeframe = false + expected.AlertId = 1 + expected.Type = "alert_graph" + + w := datadog.Widget{AlertGraphWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].AlertGraphWidget + + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "type", actualWidget.Type, expected.Type) + assertEquals(t, "viz_type", actualWidget.VizType, expected.VizType) + assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe) + assertEquals(t, "add_timeframe", actualWidget.AddTimeframe, expected.AddTimeframe) + assertEquals(t, "alert_id", actualWidget.AlertId, expected.AlertId) +} + +func TestHostMapWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.HostMapWidget + + expected.X = 1 + expected.Y = 1 + expected.Width = 5 + expected.Height = 5 + expected.TitleText = "foo" + expected.TitleAlign = "center" + expected.TitleSize = 1 + expected.Title = true + expected.Type = "check_status" + expected.Query = "avg:system.load.1{foo} by {bar}" + expected.Timeframe = "1d" + expected.Legend = true + expected.LegendSize = 5 + expected.TileDef = datadog.TileDef{} + + w := datadog.Widget{HostMapWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].HostMapWidget + + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "type", actualWidget.Type, expected.Type) + assertEquals(t, "query", actualWidget.Query, expected.Query) + assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe) + assertEquals(t, "query", actualWidget.Query, expected.Query) + assertEquals(t, "legend", actualWidget.Legend, expected.Legend) + assertEquals(t, "legend_size", actualWidget.LegendSize, expected.LegendSize) + assertTileDefEquals(t, actualWidget.TileDef, expected.TileDef) +} + +func TestCheckStatusWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.CheckStatusWidget + + expected.X = 1 + expected.Y = 1 + expected.Width = 5 + expected.Height = 5 + expected.TitleText = "foo" + expected.TitleAlign = "center" + expected.TitleSize = 1 + expected.Title = true + expected.Type = "check_status" + expected.Tags = "foo" + expected.Timeframe = "1d" + expected.Timeframe = "1d" + expected.Check = "datadog.agent.up" + expected.Group = "foo" + expected.Grouping = "check" + + w := datadog.Widget{CheckStatusWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].CheckStatusWidget + + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "type", actualWidget.Type, expected.Type) + assertEquals(t, "tags", actualWidget.Tags, expected.Tags) + assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe) + assertEquals(t, "check", actualWidget.Check, expected.Check) + assertEquals(t, "group", actualWidget.Group, expected.Group) + assertEquals(t, "grouping", actualWidget.Grouping, expected.Grouping) +} + +func TestIFrameWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.IFrameWidget + + expected.X = 1 + expected.Y = 1 + expected.Width = 5 + expected.Height = 5 + expected.TitleText = "foo" + expected.TitleAlign = "center" + expected.TitleSize = 1 + expected.Title = true + expected.Url = "http://www.example.com" + expected.Type = "iframe" + + w := datadog.Widget{IFrameWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].IFrameWidget + + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "url", actualWidget.Url, expected.Url) + assertEquals(t, "type", actualWidget.Type, expected.Type) +} + +func TestNoteWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.NoteWidget + + expected.X = 1 + expected.Y = 1 + expected.Width = 5 + expected.Height = 5 + expected.TitleText = "foo" + expected.TitleAlign = "center" + expected.TitleSize = 1 + expected.Title = true + expected.Color = "green" + expected.FontSize = 5 + expected.RefreshEvery = 60 + expected.TickPos = "foo" + expected.TickEdge = "bar" + expected.Html = "baz" + expected.Tick = false + expected.Note = "quz" + expected.AutoRefresh = false + + w := datadog.Widget{NoteWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].NoteWidget + + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "color", actualWidget.Color, expected.Color) + assertEquals(t, "front_size", actualWidget.FontSize, expected.FontSize) + assertEquals(t, "refresh_every", actualWidget.RefreshEvery, expected.RefreshEvery) + assertEquals(t, "tick_pos", actualWidget.TickPos, expected.TickPos) + assertEquals(t, "tick_edge", actualWidget.TickEdge, expected.TickEdge) + assertEquals(t, "tick", actualWidget.Tick, expected.Tick) + assertEquals(t, "html", actualWidget.Html, expected.Html) + assertEquals(t, "note", actualWidget.Note, expected.Note) + assertEquals(t, "auto_refresh", actualWidget.AutoRefresh, expected.AutoRefresh) +} + +func TestToplistWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.ToplistWidget + expected.X = 1 + expected.Y = 1 + expected.Width = 5 + expected.Height = 5 + expected.Type = "toplist" + expected.TitleText = "foo" + expected.TitleSize.Auto = false + expected.TitleSize.Size = 5 + expected.TitleAlign = "center" + expected.Title = false + expected.Timeframe = "5m" + expected.Legend = false + expected.LegendSize = 5 + + w := datadog.Widget{ToplistWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].ToplistWidget + + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "legend", actualWidget.Legend, expected.Legend) + assertEquals(t, "legend_size", actualWidget.LegendSize, expected.LegendSize) +} + +func TestEventSteamWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.EventStreamWidget + expected.EventSize = "1" + expected.Width = 1 + expected.Height = 1 + expected.X = 1 + expected.Y = 1 + expected.Query = "foo" + expected.Timeframe = "5w" + expected.Title = false + expected.TitleAlign = "center" + expected.TitleSize.Auto = false + expected.TitleSize.Size = 5 + expected.TitleText = "bar" + expected.Type = "baz" + + w := datadog.Widget{EventStreamWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].EventStreamWidget + + assertEquals(t, "event_size", actualWidget.EventSize, expected.EventSize) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "query", actualWidget.Query, expected.Query) + assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "type", actualWidget.Type, expected.Type) +} + +func TestImageWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.ImageWidget + + expected.Width = 1 + expected.Height = 1 + expected.X = 1 + expected.Y = 1 + expected.Title = false + expected.TitleAlign = "center" + expected.TitleSize.Auto = false + expected.TitleSize.Size = 5 + expected.TitleText = "bar" + expected.Type = "baz" + expected.Url = "qux" + expected.Sizing = "quuz" + + w := datadog.Widget{ImageWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].ImageWidget + + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "title_align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title_size", actualWidget.TitleSize, expected.TitleSize) + assertEquals(t, "title_text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "type", actualWidget.Type, expected.Type) + assertEquals(t, "url", actualWidget.Url, expected.Url) + assertEquals(t, "sizing", actualWidget.Sizing, expected.Sizing) +} + +func TestFreeTextWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.FreeTextWidget + + expected.X = 1 + expected.Y = 1 + expected.Height = 10 + expected.Width = 10 + expected.Text = "Test" + expected.FontSize = "16" + expected.TextAlign = "center" + + w := datadog.Widget{FreeTextWidget: expected} + + board.Widgets = append(board.Widgets, w) + + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].FreeTextWidget + + assertEquals(t, "font-size", actualWidget.FontSize, expected.FontSize) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "text", actualWidget.Text, expected.Text) + assertEquals(t, "text-align", actualWidget.TextAlign, expected.TextAlign) + assertEquals(t, "type", actualWidget.Type, expected.Type) +} + +func TestTimeseriesWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.TimeseriesWidget + expected.X = 1 + expected.Y = 1 + expected.Width = 20 + expected.Height = 30 + expected.Title = true + expected.TitleAlign = "centre" + expected.TitleSize = datadog.TextSize{Size: 16} + expected.TitleText = "Test" + expected.Timeframe = "1m" + + w := datadog.Widget{TimeseriesWidget: expected} + + board.Widgets = append(board.Widgets, w) + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].TimeseriesWidget + + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "title-align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title-size.size", actualWidget.TitleSize.Size, expected.TitleSize.Size) + assertEquals(t, "title-size.auto", actualWidget.TitleSize.Auto, expected.TitleSize.Auto) + assertEquals(t, "title-text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "type", actualWidget.Type, expected.Type) + assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe) + assertEquals(t, "legend", actualWidget.Legend, expected.Legend) + assertTileDefEquals(t, actualWidget.TileDef, expected.TileDef) +} + +func TestQueryValueWidget(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + expected := datadog.Widget{}.QueryValueWidget + expected.X = 1 + expected.Y = 1 + expected.Width = 20 + expected.Height = 30 + expected.Title = true + expected.TitleAlign = "centre" + expected.TitleSize = datadog.TextSize{Size: 16} + expected.TitleText = "Test" + expected.Timeframe = "1m" + expected.TimeframeAggregator = "sum" + expected.Aggregator = "min" + expected.Query = "docker.containers.running" + expected.MetricType = "standard" + /* TODO: add test for conditional formats + "conditional_formats": [{ + "comparator": ">", + "color": "white_on_red", + "custom_bg_color": null, + "value": 1, + "invert": false, + "custom_fg_color": null}], + */ + expected.IsValidQuery = true + expected.ResultCalcFunc = "raw" + expected.Aggregator = "avg" + expected.CalcFunc = "raw" + + w := datadog.Widget{QueryValueWidget: expected} + + board.Widgets = append(board.Widgets, w) + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed: %s", err) + } + + actualWidget := actual.Widgets[0].QueryValueWidget + + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "title-align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title-size.size", actualWidget.TitleSize.Size, expected.TitleSize.Size) + assertEquals(t, "title-size.auto", actualWidget.TitleSize.Auto, expected.TitleSize.Auto) + assertEquals(t, "title-text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "type", actualWidget.Type, expected.Type) + assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe) + assertEquals(t, "timeframe-aggregator", actualWidget.TimeframeAggregator, expected.TimeframeAggregator) + assertEquals(t, "aggregator", actualWidget.Aggregator, expected.Aggregator) + assertEquals(t, "query", actualWidget.Query, expected.Query) + assertEquals(t, "is_valid_query", actualWidget.IsValidQuery, expected.IsValidQuery) + assertEquals(t, "res_calc_func", actualWidget.ResultCalcFunc, expected.ResultCalcFunc) + assertEquals(t, "aggr", actualWidget.Aggregator, expected.Aggregator) +} + +func assertTileDefEquals(t *testing.T, actual datadog.TileDef, expected datadog.TileDef) { + assertEquals(t, "num-events", len(actual.Events), len(expected.Events)) + assertEquals(t, "num-requests", len(actual.Requests), len(expected.Requests)) + assertEquals(t, "viz", actual.Viz, expected.Viz) + + for i, event := range actual.Events { + assertEquals(t, "event-query", event.Query, expected.Events[i].Query) + } + + for i, request := range actual.Requests { + assertEquals(t, "request-query", request.Query, expected.Requests[i].Query) + assertEquals(t, "request-type", request.Type, expected.Requests[i].Type) + } +} + +func assertEquals(t *testing.T, attribute string, a, b interface{}) { + if a != b { + t.Errorf("The two %s values '%v' and '%v' are not equal", attribute, a, b) + } +} diff --git a/vendor/github.com/zorkian/go-datadog-api/integration/screenboards_test.go b/vendor/github.com/zorkian/go-datadog-api/integration/screenboards_test.go new file mode 100644 index 000000000..654d45f18 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/integration/screenboards_test.go @@ -0,0 +1,143 @@ +package integration + +import ( + "github.com/zorkian/go-datadog-api" + "testing" +) + +func init() { + client = initTest() +} + +func TestCreateAndDeleteScreenboard(t *testing.T) { + expected := getTestScreenboard() + // create the screenboard and compare it + actual, err := client.CreateScreenboard(expected) + if err != nil { + t.Fatalf("Creating a screenboard failed when it shouldn't. (%s)", err) + } + + defer cleanUpScreenboard(t, actual.Id) + + assertScreenboardEquals(t, actual, expected) + + // now try to fetch it freshly and compare it again + actual, err = client.GetScreenboard(actual.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed when it shouldn't. (%s)", err) + } + + assertScreenboardEquals(t, actual, expected) + +} + +func TestShareAndRevokeScreenboard(t *testing.T) { + expected := getTestScreenboard() + // create the screenboard + actual, err := client.CreateScreenboard(expected) + if err != nil { + t.Fatalf("Creating a screenboard failed when it shouldn't: %s", err) + } + + defer cleanUpScreenboard(t, actual.Id) + + // share screenboard and verify it was shared + var response datadog.ScreenShareResponse + err = client.ShareScreenboard(actual.Id, &response) + if err != nil { + t.Fatalf("Failed to share screenboard: %s", err) + } + + // revoke screenboard + err = client.RevokeScreenboard(actual.Id) + if err != nil { + t.Fatalf("Failed to revoke sharing of screenboard: %s", err) + } +} + +func TestUpdateScreenboard(t *testing.T) { + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + board.Title = "___New-Test-Board___" + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed when it shouldn't: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retrieving a screenboard failed when it shouldn't: %s", err) + } + + assertScreenboardEquals(t, actual, board) + +} + +func TestGetScreenboards(t *testing.T) { + boards, err := client.GetScreenboards() + if err != nil { + t.Fatalf("Retrieving screenboards failed when it shouldn't: %s", err) + } + num := len(boards) + + board := createTestScreenboard(t) + defer cleanUpScreenboard(t, board.Id) + + boards, err = client.GetScreenboards() + if err != nil { + t.Fatalf("Retrieving screenboards failed when it shouldn't: %s", err) + } + + if num+1 != len(boards) { + t.Fatalf("Number of screenboards didn't match expected: %d != %d", len(boards), num+1) + } +} + +func getTestScreenboard() *datadog.Screenboard { + return &datadog.Screenboard{ + Title: "___Test-Board___", + Height: "600", + Width: "800", + Widgets: []datadog.Widget{}, + } +} + +func createTestScreenboard(t *testing.T) *datadog.Screenboard { + board := getTestScreenboard() + board, err := client.CreateScreenboard(board) + if err != nil { + t.Fatalf("Creating a screenboard failed when it shouldn't: %s", err) + } + + return board +} + +func cleanUpScreenboard(t *testing.T, id int) { + if err := client.DeleteScreenboard(id); err != nil { + t.Fatalf("Deleting a screenboard failed when it shouldn't. Manual cleanup needed. (%s)", err) + } + + deletedBoard, err := client.GetScreenboard(id) + if deletedBoard != nil { + t.Fatal("Screenboard hasn't been deleted when it should have been. Manual cleanup needed.") + } + + if err == nil { + t.Fatal("Fetching deleted screenboard didn't lead to an error. Manual cleanup needed.") + } +} + +func assertScreenboardEquals(t *testing.T, actual, expected *datadog.Screenboard) { + if actual.Title != expected.Title { + t.Errorf("Screenboard title does not match: %s != %s", actual.Title, expected.Title) + } + if actual.Width != expected.Width { + t.Errorf("Screenboard width does not match: %s != %s", actual.Width, expected.Width) + } + if actual.Height != expected.Height { + t.Errorf("Screenboard width does not match: %s != %s", actual.Height, expected.Height) + } + if len(actual.Widgets) != len(expected.Widgets) { + t.Errorf("Number of Screenboard widgets does not match: %d != %d", len(actual.Widgets), len(expected.Widgets)) + } +} diff --git a/vendor/github.com/zorkian/go-datadog-api/integration/test_helpers.go b/vendor/github.com/zorkian/go-datadog-api/integration/test_helpers.go new file mode 100644 index 000000000..dba24dbed --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/integration/test_helpers.go @@ -0,0 +1,24 @@ +package integration + +import ( + "github.com/zorkian/go-datadog-api" + "log" + "os" +) + +var ( + apiKey string + appKey string + client *datadog.Client +) + +func initTest() *datadog.Client { + apiKey = os.Getenv("DATADOG_API_KEY") + appKey = os.Getenv("DATADOG_APP_KEY") + + if apiKey == "" || appKey == "" { + log.Fatal("Please make sure to set the env variables 'DATADOG_API_KEY' and 'DATADOG_APP_KEY' before running this test") + } + + return datadog.NewClient(apiKey, appKey) +} diff --git a/website/source/docs/providers/datadog/index.html.markdown b/website/source/docs/providers/datadog/index.html.markdown index 9e84050fe..a46943349 100644 --- a/website/source/docs/providers/datadog/index.html.markdown +++ b/website/source/docs/providers/datadog/index.html.markdown @@ -27,6 +27,11 @@ provider "datadog" { resource "datadog_monitor" "default" { ... } + +# Create a new timeboard +resource "datadog_timeboard" "default" { + ... +} ``` ## Argument Reference diff --git a/website/source/docs/providers/datadog/r/timeboard.html.markdown b/website/source/docs/providers/datadog/r/timeboard.html.markdown new file mode 100644 index 000000000..054f78fc5 --- /dev/null +++ b/website/source/docs/providers/datadog/r/timeboard.html.markdown @@ -0,0 +1,89 @@ +--- +layout: "datadog" +page_title: "Datadog: datadog_timeboard" +sidebar_current: "docs-datadog-resource-timeboard" +description: |- + Provides a Datadog timeboard resource. This can be used to create and manage timeboards. +--- + +# datadog\_timeboard + +Provides a Datadog timeboard resource. This can be used to create and manage Datadog timeboards. + +## Example Usage + +``` +# Create a new Datadog timeboard +resource "datadog_timeboard" "redis" { + + title = "Redis Timeboard (created via Terraform)" + description = "created using the Datadog provider in Terraform" + read_only = true + + 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}" + } + } + + graph { + title = "Top System CPU by Docker container" + viz = "toplist" + request { + q = "top(avg:docker.cpu.system{*} by {container_name}, 10, 'mean', 'desc')" + } + } + + template_variable { + name = "host" + prefix = "host" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `title` - (Required) The name of the dashboard. +* `description` - (Required) A description of the dashboard's content. +* `read_only` - (Optional) The read-only status of the timeboard. Default is false. +* `graph` - (Required) Nested block describing a graph definition. The structure of this block is described below. Multiple graph blocks are allowed within a datadog_timeboard resource. +* `template_variable` - (Optional) Nested block describing a template variable. The structure of this block is described below. Multiple template_variable blocks are allowed within a datadog_timeboard resource. + +### Nested `graph` blocks + +Nested `graph` blocks have the following structure: + +* `title` - (Required) The name of the graph. +* `viz` - (Required) The type of visualization to use for the graph. Valid choices are "change", "distribution", "heatmap", "hostmap", "query_value", timeseries", and "toplist". +* `request` - Nested block describing a graph definition request (a metric query to plot on the graph). The structure of this block is described below. Multiple request blocks are allowed within a graph block. + +#### Nested `graph` `request` blocks + +Nested `graph` `request` blocks have the following structure: + +* `q` - (Required) The query of the request. Pro tip: Use the JSON tab inside the Datadog UI to help build you query strings. +* `stacked` - (Optional) Boolean value to determin if this is this a stacked area graph. Default: false (line chart). + +### Nested `template_variable` blocks + +Nested `template_variable` blocks have the following structure: + +* `name` - (Required) The variable name. Can be referenced as $name in `graph` `request` `q` query strings. +* `prefix` - (Optional) The tag group. Default: no tag group. +* `default` - (Required) The default tag. Default: "*" (match all). diff --git a/website/source/layouts/datadog.erb b/website/source/layouts/datadog.erb index 77f816429..c9ce13e7d 100644 --- a/website/source/layouts/datadog.erb +++ b/website/source/layouts/datadog.erb @@ -15,6 +15,7 @@