diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 254d5a6f4..5a588e9b7 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -445,6 +445,10 @@ "ImportPath": "github.com/bgentry/speakeasy", "Rev": "36e9cfdd690967f4f690c6edcc9ffacd006014a0" }, + { + "ImportPath": "github.com/cenkalti/backoff", + "Rev": "4dc77674aceaabba2c7e3da25d4c823edfb73f99" + }, { "ImportPath": "github.com/coreos/etcd/Godeps/_workspace/src/github.com/ugorji/go/codec", "Comment": "v2.3.0-alpha.0-652-ge552791", @@ -1129,6 +1133,10 @@ "Comment": "v1.5.4-13-g75ce5fb", "Rev": "75ce5fbba34b1912a3641adbd58cf317d7315821" }, + { + "ImportPath": "github.com/zorkian/go-datadog-api", + "Rev": "632146c79714fe4232b496087802f922c1daf96f" + }, { "ImportPath": "golang.org/x/crypto/curve25519", "Rev": "1f22c0103821b9390939b6776727195525381532" diff --git a/builtin/bins/provider-datadog/main.go b/builtin/bins/provider-datadog/main.go new file mode 100644 index 000000000..05a9f31a9 --- /dev/null +++ b/builtin/bins/provider-datadog/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/datadog" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: datadog.Provider, + }) +} diff --git a/builtin/bins/provider-datadog/main_test.go b/builtin/bins/provider-datadog/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/builtin/bins/provider-datadog/main_test.go @@ -0,0 +1 @@ +package main diff --git a/builtin/providers/datadog/config.go b/builtin/providers/datadog/config.go new file mode 100644 index 000000000..61f1a60e9 --- /dev/null +++ b/builtin/providers/datadog/config.go @@ -0,0 +1,23 @@ +package datadog + +import ( + "log" + + "github.com/zorkian/go-datadog-api" +) + +// Config holds API and APP keys to authenticate to Datadog. +type Config struct { + APIKey string + APPKey string +} + +// Client returns a new Datadog client. +func (c *Config) Client() (*datadog.Client, error) { + + client := datadog.NewClient(c.APIKey, c.APPKey) + + log.Printf("[INFO] Datadog Client configured ") + + return client, nil +} diff --git a/builtin/providers/datadog/provider.go b/builtin/providers/datadog/provider.go new file mode 100644 index 000000000..7221f8481 --- /dev/null +++ b/builtin/providers/datadog/provider.go @@ -0,0 +1,42 @@ +package datadog + +import ( + "log" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "api_key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("DATADOG_API_KEY", nil), + }, + "app_key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("DATADOG_APP_KEY", nil), + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "datadog_monitor": resourceDatadogMonitor(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + + config := Config{ + APIKey: d.Get("api_key").(string), + APPKey: d.Get("app_key").(string), + } + + log.Println("[INFO] Initializing Datadog client") + return config.Client() +} diff --git a/builtin/providers/datadog/provider_test.go b/builtin/providers/datadog/provider_test.go new file mode 100644 index 000000000..b6c56102e --- /dev/null +++ b/builtin/providers/datadog/provider_test.go @@ -0,0 +1,38 @@ +package datadog + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +var testAccProviders map[string]terraform.ResourceProvider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider().(*schema.Provider) + testAccProviders = map[string]terraform.ResourceProvider{ + "datadog": testAccProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvider_impl(t *testing.T) { + var _ terraform.ResourceProvider = Provider() +} + +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("DATADOG_API_KEY"); v == "" { + t.Fatal("DATADOG_API_KEY must be set for acceptance tests") + } + if v := os.Getenv("DATADOG_APP_KEY"); v == "" { + t.Fatal("DATADOG_APP_KEY must be set for acceptance tests") + } +} diff --git a/builtin/providers/datadog/resource_datadog_monitor.go b/builtin/providers/datadog/resource_datadog_monitor.go new file mode 100644 index 000000000..af2b89f53 --- /dev/null +++ b/builtin/providers/datadog/resource_datadog_monitor.go @@ -0,0 +1,318 @@ +package datadog + +import ( + "fmt" + "log" + "strconv" + "strings" + + "encoding/json" + "github.com/hashicorp/terraform/helper/schema" + "github.com/zorkian/go-datadog-api" +) + +func resourceDatadogMonitor() *schema.Resource { + return &schema.Resource{ + Create: resourceDatadogMonitorCreate, + Read: resourceDatadogMonitorRead, + Update: resourceDatadogMonitorUpdate, + Delete: resourceDatadogMonitorDelete, + Exists: resourceDatadogMonitorExists, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "message": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "escalation_message": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "query": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + // Options + "thresholds": &schema.Schema{ + Type: schema.TypeMap, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ok": &schema.Schema{ + Type: schema.TypeFloat, + Optional: true, + }, + "warning": &schema.Schema{ + Type: schema.TypeFloat, + Optional: true, + }, + "critical": &schema.Schema{ + Type: schema.TypeFloat, + Required: true, + }, + }, + }, + }, + "notify_no_data": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "no_data_timeframe": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "renotify_interval": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "notify_audit": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + "timeout_h": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + // TODO should actually be map[string]int + "silenced": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Elem: &schema.Schema{ + Type: schema.TypeInt}, + }, + }, + "include_tags": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + }, + } +} + +func buildMonitorStruct(d *schema.ResourceData) *datadog.Monitor { + + var thresholds datadog.ThresholdCount + + if r, ok := d.GetOk("thresholds.ok"); ok { + thresholds.Ok = json.Number(r.(string)) + } + if r, ok := d.GetOk("thresholds.warning"); ok { + thresholds.Warning = json.Number(r.(string)) + } + if r, ok := d.GetOk("thresholds.critical"); ok { + thresholds.Critical = json.Number(r.(string)) + } + + o := datadog.Options{ + Thresholds: thresholds, + } + if attr, ok := d.GetOk("silenced"); ok { + s := make(map[string]int) + // TODO: this is not very defensive, test if we can fail on non int input + for k, v := range attr.(map[string]interface{}) { + s[k], _ = strconv.Atoi(v.(string)) + } + o.Silenced = s + } + if attr, ok := d.GetOk("notify_data"); ok { + o.NotifyNoData = attr.(bool) + } + if attr, ok := d.GetOk("no_data_timeframe"); ok { + o.NoDataTimeframe = attr.(int) + } + if attr, ok := d.GetOk("renotify_interval"); ok { + o.RenotifyInterval = attr.(int) + } + if attr, ok := d.GetOk("notify_audit"); ok { + o.NotifyAudit = attr.(bool) + } + if attr, ok := d.GetOk("timeout_h"); ok { + o.TimeoutH = attr.(int) + } + if attr, ok := d.GetOk("escalation_message"); ok { + o.EscalationMessage = attr.(string) + } + if attr, ok := d.GetOk("escalation_message"); ok { + o.EscalationMessage = attr.(string) + } + if attr, ok := d.GetOk("include_tags"); ok { + o.IncludeTags = attr.(bool) + } + + m := datadog.Monitor{ + Type: d.Get("type").(string), + Query: d.Get("query").(string), + Name: d.Get("name").(string), + Message: d.Get("message").(string), + Options: o, + } + + return &m +} + +func resourceDatadogMonitorExists(d *schema.ResourceData, meta interface{}) (b bool, e error) { + // Exists - This is called to verify a resource still exists. It is called prior to Read, + // and lowers the burden of Read to be able to assume the resource exists. + client := meta.(*datadog.Client) + + i, err := strconv.Atoi(d.Id()) + if err != nil { + return false, err + } + + if _, err = client.GetMonitor(i); err != nil { + if strings.Contains(err.Error(), "404 Not Found") { + return false, nil + } + return false, err + } + + return true, nil +} + +func resourceDatadogMonitorCreate(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*datadog.Client) + + m := buildMonitorStruct(d) + m, err := client.CreateMonitor(m) + if err != nil { + return fmt.Errorf("error updating montor: %s", err.Error()) + } + + d.SetId(strconv.Itoa(m.Id)) + + return nil +} + +func resourceDatadogMonitorRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*datadog.Client) + + i, err := strconv.Atoi(d.Id()) + if err != nil { + return err + } + + m, err := client.GetMonitor(i) + if err != nil { + return err + } + + log.Printf("[DEBUG] monitor: %v", m) + d.Set("name", m.Name) + d.Set("message", m.Message) + d.Set("query", m.Query) + d.Set("type", m.Type) + d.Set("thresholds", m.Options.Thresholds) + d.Set("notify_no_data", m.Options.NotifyNoData) + d.Set("notify_no_data_timeframe", m.Options.NoDataTimeframe) + d.Set("renotify_interval", m.Options.RenotifyInterval) + d.Set("notify_audit", m.Options.NotifyAudit) + d.Set("timeout_h", m.Options.TimeoutH) + d.Set("escalation_message", m.Options.EscalationMessage) + d.Set("silenced", m.Options.Silenced) + d.Set("include_tags", m.Options.IncludeTags) + + return nil +} + +func resourceDatadogMonitorUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*datadog.Client) + + m := &datadog.Monitor{} + + i, err := strconv.Atoi(d.Id()) + if err != nil { + return err + } + + m.Id = i + if attr, ok := d.GetOk("name"); ok { + m.Name = attr.(string) + } + if attr, ok := d.GetOk("message"); ok { + m.Message = attr.(string) + } + if attr, ok := d.GetOk("query"); ok { + m.Query = attr.(string) + } + + o := datadog.Options{} + if attr, ok := d.GetOk("thresholds"); ok { + thresholds := attr.(map[string]interface{}) + if thresholds["ok"] != nil { + o.Thresholds.Ok = json.Number(thresholds["ok"].(string)) + } + if thresholds["warning"] != nil { + o.Thresholds.Warning = json.Number(thresholds["warning"].(string)) + } + if thresholds["critical"] != nil { + o.Thresholds.Critical = json.Number(thresholds["critical"].(string)) + } + } + + if attr, ok := d.GetOk("notify_no_data"); ok { + o.NotifyNoData = attr.(bool) + } + if attr, ok := d.GetOk("notify_no_data_timeframe"); ok { + o.NoDataTimeframe = attr.(int) + } + if attr, ok := d.GetOk("renotify_interval"); ok { + o.RenotifyInterval = attr.(int) + } + if attr, ok := d.GetOk("notify_audit"); ok { + o.NotifyAudit = attr.(bool) + } + if attr, ok := d.GetOk("timeout_h"); ok { + o.TimeoutH = attr.(int) + } + if attr, ok := d.GetOk("escalation_message"); ok { + o.EscalationMessage = attr.(string) + } + if attr, ok := d.GetOk("silenced"); ok { + // TODO: this is not very defensive, test if we can fail non int input + s := make(map[string]int) + for k, v := range attr.(map[string]interface{}) { + s[k], _ = strconv.Atoi(v.(string)) + } + o.Silenced = s + } + if attr, ok := d.GetOk("include_tags"); ok { + o.IncludeTags = attr.(bool) + } + + m.Options = o + + if err = client.UpdateMonitor(m); err != nil { + return fmt.Errorf("error updating montor: %s", err.Error()) + } + + return resourceDatadogMonitorRead(d, meta) +} + +func resourceDatadogMonitorDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*datadog.Client) + + i, err := strconv.Atoi(d.Id()) + if err != nil { + return err + } + + if err = client.DeleteMonitor(i); err != nil { + return err + } + + return nil +} diff --git a/builtin/providers/datadog/resource_datadog_monitor_test.go b/builtin/providers/datadog/resource_datadog_monitor_test.go new file mode 100644 index 000000000..f91019d41 --- /dev/null +++ b/builtin/providers/datadog/resource_datadog_monitor_test.go @@ -0,0 +1,216 @@ +package datadog + +import ( + "fmt" + "strconv" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/zorkian/go-datadog-api" +) + +func TestAccDatadogMonitor_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDatadogMonitorDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckDatadogMonitorConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogMonitorExists("datadog_monitor.foo"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "name", "name for monitor foo"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "message", "some message Notify: @hipchat-channel"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "type", "metric alert"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "query", "avg(last_1h):avg:aws.ec2.cpu{environment:foo,host:foo} by {host} > 2"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "notify_no_data", "false"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "renotify_interval", "60"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "thresholds.ok", "0"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "thresholds.warning", "1"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "thresholds.critical", "2"), + ), + }, + }, + }) +} + +func TestAccDatadogMonitor_Updated(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDatadogMonitorDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckDatadogMonitorConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogMonitorExists("datadog_monitor.foo"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "name", "name for monitor foo"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "message", "some message Notify: @hipchat-channel"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "escalation_message", "the situation has escalated @pagerduty"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "query", "avg(last_1h):avg:aws.ec2.cpu{environment:foo,host:foo} by {host} > 2"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "type", "metric alert"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "notify_no_data", "false"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "renotify_interval", "60"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "thresholds.ok", "0"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "thresholds.warning", "1"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "thresholds.critical", "2"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "notify_audit", "false"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "timeout_h", "60"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "include_tags", "true"), + ), + }, + resource.TestStep{ + Config: testAccCheckDatadogMonitorConfigUpdated, + Check: resource.ComposeTestCheckFunc( + testAccCheckDatadogMonitorExists("datadog_monitor.foo"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "name", "name for monitor bar"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "message", "a different message Notify: @hipchat-channel"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "query", "avg(last_1h):avg:aws.ec2.cpu{environment:bar,host:bar} by {host} > 3"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "escalation_message", "the situation has escalated! @pagerduty"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "type", "metric alert"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "notify_no_data", "true"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "renotify_interval", "40"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "thresholds.ok", "0"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "thresholds.warning", "1"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "thresholds.critical", "3"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "notify_audit", "true"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "timeout_h", "70"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "include_tags", "false"), + resource.TestCheckResourceAttr( + "datadog_monitor.foo", "silenced.*", "0"), + ), + }, + }, + }) +} + +func testAccCheckDatadogMonitorDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*datadog.Client) + + if err := destroyHelper(s, client); err != nil { + return err + } + return nil +} + +func testAccCheckDatadogMonitorExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*datadog.Client) + if err := existsHelper(s, client); err != nil { + return err + } + return nil + } +} + +const testAccCheckDatadogMonitorConfig = ` +resource "datadog_monitor" "foo" { + name = "name for monitor foo" + type = "metric alert" + message = "some message Notify: @hipchat-channel" + escalation_message = "the situation has escalated @pagerduty" + + query = "avg(last_1h):avg:aws.ec2.cpu{environment:foo,host:foo} by {host} > 2" + + thresholds { + ok = 0 + warning = 1 + critical = 2 + } + + notify_no_data = false + renotify_interval = 60 + + notify_audit = false + timeout_h = 60 + include_tags = true +} +` + +const testAccCheckDatadogMonitorConfigUpdated = ` +resource "datadog_monitor" "foo" { + name = "name for monitor bar" + type = "metric alert" + message = "a different message Notify: @hipchat-channel" + escalation_message = "the situation has escalated @pagerduty" + + query = "avg(last_1h):avg:aws.ec2.cpu{environment:bar,host:bar} by {host} > 3" + + thresholds { + ok = 0 + warning = 1 + critical = 3 + } + + notify_no_data = true + renotify_interval = 40 + escalation_message = "the situation has escalated! @pagerduty" + notify_audit = true + timeout_h = 70 + include_tags = false + silenced { + "*" = 0 + } +} +` + +func destroyHelper(s *terraform.State, client *datadog.Client) error { + for _, r := range s.RootModule().Resources { + i, _ := strconv.Atoi(r.Primary.ID) + if _, err := client.GetMonitor(i); err != nil { + if strings.Contains(err.Error(), "404 Not Found") { + continue + } + return fmt.Errorf("Received an error retrieving monitor %s", err) + } + return fmt.Errorf("Monitor still exists") + } + return nil +} + +func existsHelper(s *terraform.State, client *datadog.Client) error { + for _, r := range s.RootModule().Resources { + i, _ := strconv.Atoi(r.Primary.ID) + if _, err := client.GetMonitor(i); err != nil { + return fmt.Errorf("Received an error retrieving monitor %s", err) + } + } + return nil +} diff --git a/vendor/github.com/cenkalti/backoff/.gitignore b/vendor/github.com/cenkalti/backoff/.gitignore new file mode 100644 index 000000000..00268614f --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/github.com/cenkalti/backoff/.travis.yml b/vendor/github.com/cenkalti/backoff/.travis.yml new file mode 100644 index 000000000..ce9cb6233 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/.travis.yml @@ -0,0 +1,2 @@ +language: go +go: 1.3.3 diff --git a/vendor/github.com/cenkalti/backoff/LICENSE b/vendor/github.com/cenkalti/backoff/LICENSE new file mode 100644 index 000000000..89b817996 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Cenk Altı + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/cenkalti/backoff/README.md b/vendor/github.com/cenkalti/backoff/README.md new file mode 100644 index 000000000..020b8fbf3 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/README.md @@ -0,0 +1,116 @@ +# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] + +This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client]. + +[Exponential backoff][exponential backoff wiki] +is an algorithm that uses feedback to multiplicatively decrease the rate of some process, +in order to gradually find an acceptable rate. +The retries exponentially increase and stop increasing when a certain threshold is met. + +## How To + +We define two functions, `Retry()` and `RetryNotify()`. +They receive an `Operation` to execute, a `BackOff` algorithm, +and an optional `Notify` error handler. + +The operation will be executed, and will be retried on failure with delay +as given by the backoff algorithm. The backoff algorithm can also decide when to stop +retrying. +In addition, the notify error handler will be called after each failed attempt, +except for the last time, whose error should be handled by the caller. + +```go +// An Operation is executing by Retry() or RetryNotify(). +// The operation will be retried using a backoff policy if it returns an error. +type Operation func() error + +// Notify is a notify-on-error function. It receives an operation error and +// backoff delay if the operation failed (with an error). +// +// NOTE that if the backoff policy stated to stop retrying, +// the notify function isn't called. +type Notify func(error, time.Duration) + +func Retry(Operation, BackOff) error +func RetryNotify(Operation, BackOff, Notify) +``` + +## Examples + +See more advanced examples in the [godoc][advanced example]. + +### Retry + +Simple retry helper that uses the default exponential backoff algorithm: + +```go +operation := func() error { + // An operation that might fail. + return nil // or return errors.New("some error") +} + +err := Retry(operation, NewExponentialBackOff()) +if err != nil { + // Handle error. + return err +} + +// Operation is successful. +return nil +``` + +### Ticker + +```go +operation := func() error { + // An operation that might fail + return nil // or return errors.New("some error") +} + +b := NewExponentialBackOff() +ticker := NewTicker(b) + +var err error + +// Ticks will continue to arrive when the previous operation is still running, +// so operations that take a while to fail could run in quick succession. +for range ticker.C { + if err = operation(); err != nil { + log.Println(err, "will retry...") + continue + } + + ticker.Stop() + break +} + +if err != nil { + // Operation has failed. + return err +} + +// Operation is successful. +return nil +``` + +## Getting Started + +```bash +# install +$ go get github.com/cenkalti/backoff + +# test +$ cd $GOPATH/src/github.com/cenkalti/backoff +$ go get -t ./... +$ go test -v -cover +``` + +[godoc]: https://godoc.org/github.com/cenkalti/backoff +[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png +[travis]: https://travis-ci.org/cenkalti/backoff +[travis image]: https://travis-ci.org/cenkalti/backoff.png + +[google-http-java-client]: https://github.com/google/google-http-java-client +[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff + +[advanced example]: https://godoc.org/github.com/cenkalti/backoff#example_ diff --git a/vendor/github.com/cenkalti/backoff/adv_example_test.go b/vendor/github.com/cenkalti/backoff/adv_example_test.go new file mode 100644 index 000000000..3fe6783b8 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/adv_example_test.go @@ -0,0 +1,117 @@ +package backoff + +import ( + "io/ioutil" + "log" + "net/http" + "time" +) + +// This is an example that demonstrates how this package could be used +// to perform various advanced operations. +// +// It executes an HTTP GET request with exponential backoff, +// while errors are logged and failed responses are closed, as required by net/http package. +// +// Note we define a condition function which is used inside the operation to +// determine whether the operation succeeded or failed. +func Example() error { + res, err := GetWithRetry( + "http://localhost:9999", + ErrorIfStatusCodeIsNot(http.StatusOK), + NewExponentialBackOff()) + + if err != nil { + // Close response body of last (failed) attempt. + // The Last attempt isn't handled by the notify-on-error function, + // which closes the body of all the previous attempts. + if e := res.Body.Close(); e != nil { + log.Printf("error closing last attempt's response body: %s", e) + } + log.Printf("too many failed request attempts: %s", err) + return err + } + defer res.Body.Close() // The response's Body must be closed. + + // Read body + _, _ = ioutil.ReadAll(res.Body) + + // Do more stuff + return nil +} + +// GetWithRetry is a helper function that performs an HTTP GET request +// to the given URL, and retries with the given backoff using the given condition function. +// +// It also uses a notify-on-error function which logs +// and closes the response body of the failed request. +func GetWithRetry(url string, condition Condition, bck BackOff) (*http.Response, error) { + var res *http.Response + err := RetryNotify( + func() error { + var err error + res, err = http.Get(url) + if err != nil { + return err + } + return condition(res) + }, + bck, + LogAndClose()) + + return res, err +} + +// Condition is a retry condition function. +// It receives a response, and returns an error +// if the response failed the condition. +type Condition func(*http.Response) error + +// ErrorIfStatusCodeIsNot returns a retry condition function. +// The condition returns an error +// if the given response's status code is not the given HTTP status code. +func ErrorIfStatusCodeIsNot(status int) Condition { + return func(res *http.Response) error { + if res.StatusCode != status { + return NewError(res) + } + return nil + } +} + +// Error is returned on ErrorIfX() condition functions throughout this package. +type Error struct { + Response *http.Response +} + +func NewError(res *http.Response) *Error { + // Sanity check + if res == nil { + panic("response object is nil") + } + return &Error{Response: res} +} +func (err *Error) Error() string { return "request failed" } + +// LogAndClose is a notify-on-error function. +// It logs the error and closes the response body. +func LogAndClose() Notify { + return func(err error, wait time.Duration) { + switch e := err.(type) { + case *Error: + defer e.Response.Body.Close() + + b, err := ioutil.ReadAll(e.Response.Body) + var body string + if err != nil { + body = "can't read body" + } else { + body = string(b) + } + + log.Printf("%s: %s", e.Response.Status, body) + default: + log.Println(err) + } + } +} diff --git a/vendor/github.com/cenkalti/backoff/backoff.go b/vendor/github.com/cenkalti/backoff/backoff.go new file mode 100644 index 000000000..61bd6df66 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/backoff.go @@ -0,0 +1,59 @@ +// Package backoff implements backoff algorithms for retrying operations. +// +// Also has a Retry() helper for retrying operations that may fail. +package backoff + +import "time" + +// BackOff is a backoff policy for retrying an operation. +type BackOff interface { + // NextBackOff returns the duration to wait before retrying the operation, + // or backoff.Stop to indicate that no more retries should be made. + // + // Example usage: + // + // duration := backoff.NextBackOff(); + // if (duration == backoff.Stop) { + // // Do not retry operation. + // } else { + // // Sleep for duration and retry operation. + // } + // + NextBackOff() time.Duration + + // Reset to initial state. + Reset() +} + +// Indicates that no more retries should be made for use in NextBackOff(). +const Stop time.Duration = -1 + +// ZeroBackOff is a fixed backoff policy whose backoff time is always zero, +// meaning that the operation is retried immediately without waiting, indefinitely. +type ZeroBackOff struct{} + +func (b *ZeroBackOff) Reset() {} + +func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 } + +// StopBackOff is a fixed backoff policy that always returns backoff.Stop for +// NextBackOff(), meaning that the operation should never be retried. +type StopBackOff struct{} + +func (b *StopBackOff) Reset() {} + +func (b *StopBackOff) NextBackOff() time.Duration { return Stop } + +// ConstantBackOff is a backoff policy that always returns the same backoff delay. +// This is in contrast to an exponential backoff policy, +// which returns a delay that grows longer as you call NextBackOff() over and over again. +type ConstantBackOff struct { + Interval time.Duration +} + +func (b *ConstantBackOff) Reset() {} +func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval } + +func NewConstantBackOff(d time.Duration) *ConstantBackOff { + return &ConstantBackOff{Interval: d} +} diff --git a/vendor/github.com/cenkalti/backoff/backoff_test.go b/vendor/github.com/cenkalti/backoff/backoff_test.go new file mode 100644 index 000000000..91f27c4f1 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/backoff_test.go @@ -0,0 +1,27 @@ +package backoff + +import ( + "testing" + "time" +) + +func TestNextBackOffMillis(t *testing.T) { + subtestNextBackOff(t, 0, new(ZeroBackOff)) + subtestNextBackOff(t, Stop, new(StopBackOff)) +} + +func subtestNextBackOff(t *testing.T, expectedValue time.Duration, backOffPolicy BackOff) { + for i := 0; i < 10; i++ { + next := backOffPolicy.NextBackOff() + if next != expectedValue { + t.Errorf("got: %d expected: %d", next, expectedValue) + } + } +} + +func TestConstantBackOff(t *testing.T) { + backoff := NewConstantBackOff(time.Second) + if backoff.NextBackOff() != time.Second { + t.Error("invalid interval") + } +} diff --git a/vendor/github.com/cenkalti/backoff/example_test.go b/vendor/github.com/cenkalti/backoff/example_test.go new file mode 100644 index 000000000..0d1852e45 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/example_test.go @@ -0,0 +1,51 @@ +package backoff + +import "log" + +func ExampleRetry() error { + operation := func() error { + // An operation that might fail. + return nil // or return errors.New("some error") + } + + err := Retry(operation, NewExponentialBackOff()) + if err != nil { + // Handle error. + return err + } + + // Operation is successful. + return nil +} + +func ExampleTicker() error { + operation := func() error { + // An operation that might fail + return nil // or return errors.New("some error") + } + + b := NewExponentialBackOff() + ticker := NewTicker(b) + + var err error + + // Ticks will continue to arrive when the previous operation is still running, + // so operations that take a while to fail could run in quick succession. + for _ = range ticker.C { + if err = operation(); err != nil { + log.Println(err, "will retry...") + continue + } + + ticker.Stop() + break + } + + if err != nil { + // Operation has failed. + return err + } + + // Operation is successful. + return nil +} diff --git a/vendor/github.com/cenkalti/backoff/exponential.go b/vendor/github.com/cenkalti/backoff/exponential.go new file mode 100644 index 000000000..cc2a164f2 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/exponential.go @@ -0,0 +1,151 @@ +package backoff + +import ( + "math/rand" + "time" +) + +/* +ExponentialBackOff is a backoff implementation that increases the backoff +period for each retry attempt using a randomization function that grows exponentially. + +NextBackOff() is calculated using the following formula: + + randomized interval = + RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor]) + +In other words NextBackOff() will range between the randomization factor +percentage below and above the retry interval. + +For example, given the following parameters: + + RetryInterval = 2 + RandomizationFactor = 0.5 + Multiplier = 2 + +the actual backoff period used in the next retry attempt will range between 1 and 3 seconds, +multiplied by the exponential, that is, between 2 and 6 seconds. + +Note: MaxInterval caps the RetryInterval and not the randomized interval. + +If the time elapsed since an ExponentialBackOff instance is created goes past the +MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop. + +The elapsed time can be reset by calling Reset(). + +Example: Given the following default arguments, for 10 tries the sequence will be, +and assuming we go over the MaxElapsedTime on the 10th try: + + Request # RetryInterval (seconds) Randomized Interval (seconds) + + 1 0.5 [0.25, 0.75] + 2 0.75 [0.375, 1.125] + 3 1.125 [0.562, 1.687] + 4 1.687 [0.8435, 2.53] + 5 2.53 [1.265, 3.795] + 6 3.795 [1.897, 5.692] + 7 5.692 [2.846, 8.538] + 8 8.538 [4.269, 12.807] + 9 12.807 [6.403, 19.210] + 10 19.210 backoff.Stop + +Note: Implementation is not thread-safe. +*/ +type ExponentialBackOff struct { + InitialInterval time.Duration + RandomizationFactor float64 + Multiplier float64 + MaxInterval time.Duration + // After MaxElapsedTime the ExponentialBackOff stops. + // It never stops if MaxElapsedTime == 0. + MaxElapsedTime time.Duration + Clock Clock + + currentInterval time.Duration + startTime time.Time +} + +// Clock is an interface that returns current time for BackOff. +type Clock interface { + Now() time.Time +} + +// Default values for ExponentialBackOff. +const ( + DefaultInitialInterval = 500 * time.Millisecond + DefaultRandomizationFactor = 0.5 + DefaultMultiplier = 1.5 + DefaultMaxInterval = 60 * time.Second + DefaultMaxElapsedTime = 15 * time.Minute +) + +// NewExponentialBackOff creates an instance of ExponentialBackOff using default values. +func NewExponentialBackOff() *ExponentialBackOff { + b := &ExponentialBackOff{ + InitialInterval: DefaultInitialInterval, + RandomizationFactor: DefaultRandomizationFactor, + Multiplier: DefaultMultiplier, + MaxInterval: DefaultMaxInterval, + MaxElapsedTime: DefaultMaxElapsedTime, + Clock: SystemClock, + } + b.Reset() + return b +} + +type systemClock struct{} + +func (t systemClock) Now() time.Time { + return time.Now() +} + +// SystemClock implements Clock interface that uses time.Now(). +var SystemClock = systemClock{} + +// Reset the interval back to the initial retry interval and restarts the timer. +func (b *ExponentialBackOff) Reset() { + b.currentInterval = b.InitialInterval + b.startTime = b.Clock.Now() +} + +// NextBackOff calculates the next backoff interval using the formula: +// Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval) +func (b *ExponentialBackOff) NextBackOff() time.Duration { + // Make sure we have not gone over the maximum elapsed time. + if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime { + return Stop + } + defer b.incrementCurrentInterval() + return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval) +} + +// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance +// is created and is reset when Reset() is called. +// +// The elapsed time is computed using time.Now().UnixNano(). +func (b *ExponentialBackOff) GetElapsedTime() time.Duration { + return b.Clock.Now().Sub(b.startTime) +} + +// Increments the current interval by multiplying it with the multiplier. +func (b *ExponentialBackOff) incrementCurrentInterval() { + // Check for overflow, if overflow is detected set the current interval to the max interval. + if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier { + b.currentInterval = b.MaxInterval + } else { + b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier) + } +} + +// Returns a random value from the following interval: +// [randomizationFactor * currentInterval, randomizationFactor * currentInterval]. +func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration { + var delta = randomizationFactor * float64(currentInterval) + var minInterval = float64(currentInterval) - delta + var maxInterval = float64(currentInterval) + delta + + // Get a random value from the range [minInterval, maxInterval]. + // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then + // we want a 33% chance for selecting either 1, 2 or 3. + return time.Duration(minInterval + (random * (maxInterval - minInterval + 1))) +} diff --git a/vendor/github.com/cenkalti/backoff/exponential_test.go b/vendor/github.com/cenkalti/backoff/exponential_test.go new file mode 100644 index 000000000..11b95e4f6 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/exponential_test.go @@ -0,0 +1,108 @@ +package backoff + +import ( + "math" + "testing" + "time" +) + +func TestBackOff(t *testing.T) { + var ( + testInitialInterval = 500 * time.Millisecond + testRandomizationFactor = 0.1 + testMultiplier = 2.0 + testMaxInterval = 5 * time.Second + testMaxElapsedTime = 15 * time.Minute + ) + + exp := NewExponentialBackOff() + exp.InitialInterval = testInitialInterval + exp.RandomizationFactor = testRandomizationFactor + exp.Multiplier = testMultiplier + exp.MaxInterval = testMaxInterval + exp.MaxElapsedTime = testMaxElapsedTime + exp.Reset() + + var expectedResults = []time.Duration{500, 1000, 2000, 4000, 5000, 5000, 5000, 5000, 5000, 5000} + for i, d := range expectedResults { + expectedResults[i] = d * time.Millisecond + } + + for _, expected := range expectedResults { + assertEquals(t, expected, exp.currentInterval) + // Assert that the next backoff falls in the expected range. + var minInterval = expected - time.Duration(testRandomizationFactor*float64(expected)) + var maxInterval = expected + time.Duration(testRandomizationFactor*float64(expected)) + var actualInterval = exp.NextBackOff() + if !(minInterval <= actualInterval && actualInterval <= maxInterval) { + t.Error("error") + } + } +} + +func TestGetRandomizedInterval(t *testing.T) { + // 33% chance of being 1. + assertEquals(t, 1, getRandomValueFromInterval(0.5, 0, 2)) + assertEquals(t, 1, getRandomValueFromInterval(0.5, 0.33, 2)) + // 33% chance of being 2. + assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.34, 2)) + assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.66, 2)) + // 33% chance of being 3. + assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.67, 2)) + assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.99, 2)) +} + +type TestClock struct { + i time.Duration + start time.Time +} + +func (c *TestClock) Now() time.Time { + t := c.start.Add(c.i) + c.i += time.Second + return t +} + +func TestGetElapsedTime(t *testing.T) { + var exp = NewExponentialBackOff() + exp.Clock = &TestClock{} + exp.Reset() + + var elapsedTime = exp.GetElapsedTime() + if elapsedTime != time.Second { + t.Errorf("elapsedTime=%d", elapsedTime) + } +} + +func TestMaxElapsedTime(t *testing.T) { + var exp = NewExponentialBackOff() + exp.Clock = &TestClock{start: time.Time{}.Add(10000 * time.Second)} + // Change the currentElapsedTime to be 0 ensuring that the elapsed time will be greater + // than the max elapsed time. + exp.startTime = time.Time{} + assertEquals(t, Stop, exp.NextBackOff()) +} + +func TestBackOffOverflow(t *testing.T) { + var ( + testInitialInterval time.Duration = math.MaxInt64 / 2 + testMaxInterval time.Duration = math.MaxInt64 + testMultiplier = 2.1 + ) + + exp := NewExponentialBackOff() + exp.InitialInterval = testInitialInterval + exp.Multiplier = testMultiplier + exp.MaxInterval = testMaxInterval + exp.Reset() + + exp.NextBackOff() + // Assert that when an overflow is possible the current varerval time.Duration is set to the max varerval time.Duration . + assertEquals(t, testMaxInterval, exp.currentInterval) +} + +func assertEquals(t *testing.T, expected, value time.Duration) { + if expected != value { + t.Errorf("got: %d, expected: %d", value, expected) + } +} diff --git a/vendor/github.com/cenkalti/backoff/retry.go b/vendor/github.com/cenkalti/backoff/retry.go new file mode 100644 index 000000000..f01f2bbd0 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/retry.go @@ -0,0 +1,46 @@ +package backoff + +import "time" + +// An Operation is executing by Retry() or RetryNotify(). +// The operation will be retried using a backoff policy if it returns an error. +type Operation func() error + +// Notify is a notify-on-error function. It receives an operation error and +// backoff delay if the operation failed (with an error). +// +// NOTE that if the backoff policy stated to stop retrying, +// the notify function isn't called. +type Notify func(error, time.Duration) + +// Retry the function f until it does not return error or BackOff stops. +// f is guaranteed to be run at least once. +// It is the caller's responsibility to reset b after Retry returns. +// +// Retry sleeps the goroutine for the duration returned by BackOff after a +// failed operation returns. +func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) } + +// RetryNotify calls notify function with the error and wait duration +// for each failed attempt before sleep. +func RetryNotify(operation Operation, b BackOff, notify Notify) error { + var err error + var next time.Duration + + b.Reset() + for { + if err = operation(); err == nil { + return nil + } + + if next = b.NextBackOff(); next == Stop { + return err + } + + if notify != nil { + notify(err, next) + } + + time.Sleep(next) + } +} diff --git a/vendor/github.com/cenkalti/backoff/retry_test.go b/vendor/github.com/cenkalti/backoff/retry_test.go new file mode 100644 index 000000000..c0d25ab76 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/retry_test.go @@ -0,0 +1,34 @@ +package backoff + +import ( + "errors" + "log" + "testing" +) + +func TestRetry(t *testing.T) { + const successOn = 3 + var i = 0 + + // This function is successfull on "successOn" calls. + f := func() error { + i++ + log.Printf("function is called %d. time\n", i) + + if i == successOn { + log.Println("OK") + return nil + } + + log.Println("error") + return errors.New("error") + } + + err := Retry(f, NewExponentialBackOff()) + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if i != successOn { + t.Errorf("invalid number of retries: %d", i) + } +} diff --git a/vendor/github.com/cenkalti/backoff/ticker.go b/vendor/github.com/cenkalti/backoff/ticker.go new file mode 100644 index 000000000..7a5ff4ed1 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/ticker.go @@ -0,0 +1,79 @@ +package backoff + +import ( + "runtime" + "sync" + "time" +) + +// Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff. +// +// Ticks will continue to arrive when the previous operation is still running, +// so operations that take a while to fail could run in quick succession. +type Ticker struct { + C <-chan time.Time + c chan time.Time + b BackOff + stop chan struct{} + stopOnce sync.Once +} + +// NewTicker returns a new Ticker containing a channel that will send the time at times +// specified by the BackOff argument. Ticker is guaranteed to tick at least once. +// The channel is closed when Stop method is called or BackOff stops. +func NewTicker(b BackOff) *Ticker { + c := make(chan time.Time) + t := &Ticker{ + C: c, + c: c, + b: b, + stop: make(chan struct{}), + } + go t.run() + runtime.SetFinalizer(t, (*Ticker).Stop) + return t +} + +// Stop turns off a ticker. After Stop, no more ticks will be sent. +func (t *Ticker) Stop() { + t.stopOnce.Do(func() { close(t.stop) }) +} + +func (t *Ticker) run() { + c := t.c + defer close(c) + t.b.Reset() + + // Ticker is guaranteed to tick at least once. + afterC := t.send(time.Now()) + + for { + if afterC == nil { + return + } + + select { + case tick := <-afterC: + afterC = t.send(tick) + case <-t.stop: + t.c = nil // Prevent future ticks from being sent to the channel. + return + } + } +} + +func (t *Ticker) send(tick time.Time) <-chan time.Time { + select { + case t.c <- tick: + case <-t.stop: + return nil + } + + next := t.b.NextBackOff() + if next == Stop { + t.Stop() + return nil + } + + return time.After(next) +} diff --git a/vendor/github.com/cenkalti/backoff/ticker_test.go b/vendor/github.com/cenkalti/backoff/ticker_test.go new file mode 100644 index 000000000..7c392df46 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/ticker_test.go @@ -0,0 +1,45 @@ +package backoff + +import ( + "errors" + "log" + "testing" +) + +func TestTicker(t *testing.T) { + const successOn = 3 + var i = 0 + + // This function is successfull on "successOn" calls. + f := func() error { + i++ + log.Printf("function is called %d. time\n", i) + + if i == successOn { + log.Println("OK") + return nil + } + + log.Println("error") + return errors.New("error") + } + + b := NewExponentialBackOff() + ticker := NewTicker(b) + + var err error + for _ = range ticker.C { + if err = f(); err != nil { + t.Log(err) + continue + } + + break + } + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if i != successOn { + t.Errorf("invalid number of retries: %d", i) + } +} diff --git a/vendor/github.com/zorkian/go-datadog-api/LICENSE b/vendor/github.com/zorkian/go-datadog-api/LICENSE new file mode 100644 index 000000000..f0903d29a --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2013 by authors and contributors. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/zorkian/go-datadog-api/Makefile b/vendor/github.com/zorkian/go-datadog-api/Makefile new file mode 100644 index 000000000..b2256db53 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/Makefile @@ -0,0 +1,42 @@ +TEST?=. +VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr + +default: test + +# get dependencies +updatedeps: + go list ./... \ + | xargs go list -f '{{join .Deps "\n"}}' \ + | grep -v go-datadog-api\ + | grep -v '/internal/' \ + | sort -u \ + | xargs go get -f -u -v + +# test runs the unit tests and vets the code +test: + go test . $(TESTARGS) -v -timeout=30s -parallel=4 + @$(MAKE) vet + +# testacc runs acceptance tests +testacc: + go test integration/* -v $(TESTARGS) -timeout 90m + +# testrace runs the race checker +testrace: + go test -race $(TEST) $(TESTARGS) + +# vet runs the Go source code static analysis tool `vet` to find +# any common errors. +vet: + @go tool vet 2>/dev/null ; if [ $$? -eq 3 ]; then \ + go get golang.org/x/tools/cmd/vet; \ + fi + @echo "go tool vet $(VETARGS) $(TEST) " + @go tool vet $(VETARGS) $(TEST) ; if [ $$? -eq 1 ]; then \ + echo ""; \ + echo "Vet found suspicious constructs. Please check the reported constructs"; \ + echo "and fix them if necessary before submitting the code for review."; \ + exit 1; \ + fi + +.PHONY: default test testacc updatedeps vet diff --git a/vendor/github.com/zorkian/go-datadog-api/README.md b/vendor/github.com/zorkian/go-datadog-api/README.md new file mode 100644 index 000000000..89cd20d10 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/README.md @@ -0,0 +1,69 @@ +[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/zorkian/go-datadog-api) +[![Build +status](https://travis-ci.org/zorkian/go-datadog-api.svg)](https://travis-ci.org/zorkian/go-datadog-api) + +# Datadog API in Go + +Hi! + +This is a Go wrapper for the Datadog API. You should use this library if you need to interact +with the Datadog system. You can post metrics with it if you want, but this library is probably +mostly used for automating dashboards/alerting and retrieving data (events, etc). + +The source API documentation is here: + +## USAGE + +To use this project, include it in your code like: + +``` go + import "github.com/zorkian/go-datadog-api" +``` + +Then, you can work with it: + +``` go + client := datadog.NewClient("api key", "application key") + + dash, err := client.GetDashboard(10880) + if err != nil { + log.Fatalf("fatal: %s\n", err) + } + log.Printf("dashboard %d: %s\n", dash.Id, dash.Title) +``` + +That's all; it's pretty easy to use. Check out the Godoc link for the +available API methods and, if you can't find the one you need, +let us know (or patches welcome)! + +## DOCUMENTATION + +Please see: + +## BUGS/PROBLEMS/CONTRIBUTING + +There are certainly some, but presently no known major bugs. If you do +find something that doesn't work as expected, please file an issue on +Github: + + + +Thanks in advance! And, as always, patches welcome! + +## DEVELOPMENT + +* Get dependencies with `make updatedeps`. +* Run tests tests with `make test`. +* Integration tests can be run with `make testacc`. + +The acceptance tests require _DATADOG_API_KEY_ and _DATADOG_APP_KEY_ to be available +in your environment variables. + +*Warning: the integrations tests will create and remove real resources in your Datadog +account* + +## COPYRIGHT AND LICENSE + +Please see the LICENSE file for the included license information. + +Copyright 2013 by authors and contributors. diff --git a/vendor/github.com/zorkian/go-datadog-api/alerts.go b/vendor/github.com/zorkian/go-datadog-api/alerts.go new file mode 100644 index 000000000..c12296451 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/alerts.go @@ -0,0 +1,85 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +import ( + "fmt" +) + +// Alert represents the data of an alert: a query that can fire and send a +// message to the users. +type Alert struct { + Id int `json:"id,omitempty"` + Creator int `json:"creator,omitempty"` + Query string `json:"query,omitempty"` + Name string `json:"name,omitempty"` + Message string `json:"message,omitempty"` + Silenced bool `json:"silenced,omitempty"` + NotifyNoData bool `json:"notify_no_data,omitempty"` + State string `json:"state,omitempty"` +} + +// reqAlerts receives a slice of all alerts. +type reqAlerts struct { + Alerts []Alert `json:"alerts,omitempty"` +} + +// CreateAlert adds a new alert to the system. This returns a pointer to an +// Alert so you can pass that to UpdateAlert later if needed. +func (self *Client) CreateAlert(alert *Alert) (*Alert, error) { + var out Alert + err := self.doJsonRequest("POST", "/v1/alert", alert, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +// UpdateAlert takes an alert that was previously retrieved through some method +// and sends it back to the server. +func (self *Client) UpdateAlert(alert *Alert) error { + return self.doJsonRequest("PUT", fmt.Sprintf("/v1/alert/%d", alert.Id), + alert, nil) +} + +// GetAlert retrieves an alert by identifier. +func (self *Client) GetAlert(id int) (*Alert, error) { + var out Alert + err := self.doJsonRequest("GET", fmt.Sprintf("/v1/alert/%d", id), nil, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +// DeleteAlert removes an alert from the system. +func (self *Client) DeleteAlert(id int) error { + return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/alert/%d", id), + nil, nil) +} + +// GetAlerts returns a slice of all alerts. +func (self *Client) GetAlerts() ([]Alert, error) { + var out reqAlerts + err := self.doJsonRequest("GET", "/v1/alert", nil, &out) + if err != nil { + return nil, err + } + return out.Alerts, nil +} + +// MuteAlerts turns off alerting notifications. +func (self *Client) MuteAlerts() error { + return self.doJsonRequest("POST", "/v1/mute_alerts", nil, nil) +} + +// UnmuteAlerts turns on alerting notifications. +func (self *Client) UnmuteAlerts() error { + return self.doJsonRequest("POST", "/v1/unmute_alerts", nil, nil) +} diff --git a/vendor/github.com/zorkian/go-datadog-api/comments.go b/vendor/github.com/zorkian/go-datadog-api/comments.go new file mode 100644 index 000000000..b16e3e96f --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/comments.go @@ -0,0 +1,65 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +import ( + "fmt" +) + +// Comment is a special form of event that appears in a stream. +type Comment struct { + Id int `json:"id"` + RelatedId int `json:"related_event_id"` + Handle string `json:"handle"` + Message string `json:"message"` + Resource string `json:"resource"` + Url string `json:"url"` +} + +// reqComment is the container for receiving commenst. +type reqComment struct { + Comment Comment `json:"comment"` +} + +// CreateComment adds a new comment to the system. +func (self *Client) CreateComment(handle, message string) (*Comment, error) { + var out reqComment + comment := Comment{Handle: handle, Message: message} + err := self.doJsonRequest("POST", "/v1/comments", &comment, &out) + if err != nil { + return nil, err + } + return &out.Comment, nil +} + +// CreateRelatedComment adds a new comment, but lets you specify the related +// identifier for the comment. +func (self *Client) CreateRelatedComment(handle, message string, + relid int) (*Comment, error) { + var out reqComment + comment := Comment{Handle: handle, Message: message, RelatedId: relid} + err := self.doJsonRequest("POST", "/v1/comments", &comment, &out) + if err != nil { + return nil, err + } + return &out.Comment, nil +} + +// EditComment changes the message and possibly handle of a particular comment. +func (self *Client) EditComment(id int, handle, message string) error { + comment := Comment{Handle: handle, Message: message} + return self.doJsonRequest("PUT", fmt.Sprintf("/v1/comments/%d", id), + &comment, nil) +} + +// DeleteComment does exactly what you expect. +func (self *Client) DeleteComment(id int) error { + return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/comments/%d", id), + nil, nil) +} diff --git a/vendor/github.com/zorkian/go-datadog-api/dashboards.go b/vendor/github.com/zorkian/go-datadog-api/dashboards.go new file mode 100644 index 000000000..c21a34ed4 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/dashboards.go @@ -0,0 +1,107 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +import ( + "fmt" +) + +// Graph represents a graph that might exist on a dashboard. +type Graph struct { + Title string `json:"title"` + Events []struct{} `json:"events"` + Definition struct { + Viz string `json:"viz"` + Requests []struct { + Query string `json:"q"` + Stacked bool `json:"stacked"` + } `json:"requests"` + } `json:"definition"` +} + +// Template variable represents a template variable that might exist on a dashboard +type TemplateVariable struct { + Name string `json:"name"` + Prefix string `json:"prefix"` + Default string `json:"default"` +} + +// Dashboard represents a user created dashboard. This is the full dashboard +// struct when we load a dashboard in detail. +type Dashboard struct { + Id int `json:"id"` + Description string `json:"description"` + Title string `json:"title"` + Graphs []Graph `json:"graphs"` + TemplateVariables []TemplateVariable `json:"template_variables,omitempty"` +} + +// DashboardLite represents a user created dashboard. This is the mini +// struct when we load the summaries. +type DashboardLite struct { + Id int `json:"id,string"` // TODO: Remove ',string'. + Resource string `json:"resource"` + Description string `json:"description"` + Title string `json:"title"` +} + +// reqGetDashboards from /api/v1/dash +type reqGetDashboards struct { + Dashboards []DashboardLite `json:"dashes"` +} + +// reqGetDashboard from /api/v1/dash/:dashboard_id +type reqGetDashboard struct { + Resource string `json:"resource"` + Url string `json:"url"` + Dashboard Dashboard `json:"dash"` +} + +// GetDashboard returns a single dashboard created on this account. +func (self *Client) GetDashboard(id int) (*Dashboard, error) { + var out reqGetDashboard + err := self.doJsonRequest("GET", fmt.Sprintf("/v1/dash/%d", id), nil, &out) + if err != nil { + return nil, err + } + return &out.Dashboard, nil +} + +// GetDashboards returns a list of all dashboards created on this account. +func (self *Client) GetDashboards() ([]DashboardLite, error) { + var out reqGetDashboards + err := self.doJsonRequest("GET", "/v1/dash", nil, &out) + if err != nil { + return nil, err + } + return out.Dashboards, nil +} + +// DeleteDashboard deletes a dashboard by the identifier. +func (self *Client) DeleteDashboard(id int) error { + return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/dash/%d", id), nil, nil) +} + +// CreateDashboard creates a new dashboard when given a Dashboard struct. Note +// that the Id, Resource, Url and similar elements are not used in creation. +func (self *Client) CreateDashboard(dash *Dashboard) (*Dashboard, error) { + var out reqGetDashboard + err := self.doJsonRequest("POST", "/v1/dash", dash, &out) + if err != nil { + return nil, err + } + return &out.Dashboard, nil +} + +// UpdateDashboard in essence takes a Dashboard struct and persists it back to +// the server. Use this if you've updated your local and need to push it back. +func (self *Client) UpdateDashboard(dash *Dashboard) error { + return self.doJsonRequest("PUT", fmt.Sprintf("/v1/dash/%d", dash.Id), + dash, nil) +} diff --git a/vendor/github.com/zorkian/go-datadog-api/downtimes.go b/vendor/github.com/zorkian/go-datadog-api/downtimes.go new file mode 100644 index 000000000..6ff16ab57 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/downtimes.go @@ -0,0 +1,83 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +import ( + "fmt" +) + +type Recurrence struct { + Period int `json:"period,omitempty"` + Type string `json:"type,omitempty"` + UntilDate int `json:"until_date,omitempty"` + UntilOccurrences int `json:"until_occurrences,omitempty"` + WeekDays []string `json:"week_days,omitempty"` +} + +type Downtime struct { + Active bool `json:"active,omitempty"` + Canceled int `json:"canceled,omitempty"` + Disabled bool `json:"disabled,omitempty"` + End int `json:"end,omitempty"` + Id int `json:"id,omitempty"` + Message string `json:"message,omitempty"` + Recurrence *Recurrence `json:"recurrence,omitempty"` + Scope []string `json:"scope,omitempty"` + Start int `json:"start,omitempty"` +} + +// reqDowntimes retrieves a slice of all Downtimes. +type reqDowntimes struct { + Downtimes []Downtime `json:"downtimes,omitempty"` +} + +// CreateDowntime adds a new downtme to the system. This returns a pointer +// to a Downtime so you can pass that to UpdateDowntime or CancelDowntime +// later if needed. +func (self *Client) CreateDowntime(downtime *Downtime) (*Downtime, error) { + var out Downtime + err := self.doJsonRequest("POST", "/v1/downtime", downtime, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +// UpdateDowntime takes a downtime that was previously retrieved through some method +// and sends it back to the server. +func (self *Client) UpdateDowntime(downtime *Downtime) error { + return self.doJsonRequest("PUT", fmt.Sprintf("/v1/downtime/%d", downtime.Id), + downtime, nil) +} + +// Getdowntime retrieves an downtime by identifier. +func (self *Client) GetDowntime(id int) (*Downtime, error) { + var out Downtime + err := self.doJsonRequest("GET", fmt.Sprintf("/v1/downtime/%d", id), nil, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +// DeleteDowntime removes an downtime from the system. +func (self *Client) DeleteDowntime(id int) error { + return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/downtime/%d", id), + nil, nil) +} + +// GetDowntimes returns a slice of all downtimes. +func (self *Client) GetDowntimes() ([]Downtime, error) { + var out reqDowntimes + err := self.doJsonRequest("GET", "/v1/downtime", nil, &out.Downtimes) + if err != nil { + return nil, err + } + return out.Downtimes, nil +} diff --git a/vendor/github.com/zorkian/go-datadog-api/events.go b/vendor/github.com/zorkian/go-datadog-api/events.go new file mode 100644 index 000000000..2278a120a --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/events.go @@ -0,0 +1,89 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +import ( + "fmt" + "net/url" + "strconv" +) + +// Event is a single event. If this is being used to post an event, then not +// all fields will be filled out. +type Event struct { + Id int `json:"id,omitempty"` + Title string `json:"title,omitempty"` + Text string `json:"text,omitempty"` + Time int `json:"date_happened,omitempty"` // UNIX time. + Priority string `json:"priority,omitempty"` + AlertType string `json:"alert_type,omitempty"` + Host string `json:"host,omitempty"` + Aggregation string `json:"aggregation_key,omitempty"` + SourceType string `json:"source_type,omitempty"` + Tags []string `json:"tags,omitempty"` + Url string `json:"url,omitempty"` + Resource string `json:"resource,omitempty"` +} + +// reqGetEvent is the container for receiving a single event. +type reqGetEvent struct { + Event Event `json:"event,omitempty"` +} + +// reqGetEvents is for returning many events. +type reqGetEvents struct { + Events []Event `json:"events,omitempty"` +} + +// PostEvent takes as input an event and then posts it to the server. +func (self *Client) PostEvent(event *Event) (*Event, error) { + var out reqGetEvent + err := self.doJsonRequest("POST", "/v1/events", event, &out) + if err != nil { + return nil, err + } + return &out.Event, nil +} + +// GetEvent gets a single event given an identifier. +func (self *Client) GetEvent(id int) (*Event, error) { + var out reqGetEvent + err := self.doJsonRequest("GET", fmt.Sprintf("/v1/events/%d", id), nil, &out) + if err != nil { + return nil, err + } + return &out.Event, nil +} + +// QueryEvents returns a slice of events from the query stream. +func (self *Client) GetEvents(start, end int, + priority, sources, tags string) ([]Event, error) { + // Since this is a GET request, we need to build a query string. + vals := url.Values{} + vals.Add("start", strconv.Itoa(start)) + vals.Add("end", strconv.Itoa(end)) + if priority != "" { + vals.Add("priority", priority) + } + if sources != "" { + vals.Add("sources", sources) + } + if tags != "" { + vals.Add("tags", tags) + } + + // Now the request and response. + var out reqGetEvents + err := self.doJsonRequest("GET", + fmt.Sprintf("/v1/events?%s", vals.Encode()), nil, &out) + if err != nil { + return nil, err + } + return out.Events, nil +} 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/vendor/github.com/zorkian/go-datadog-api/main.go b/vendor/github.com/zorkian/go-datadog-api/main.go new file mode 100644 index 000000000..10ec467d4 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/main.go @@ -0,0 +1,30 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +import "net/http" + +// Client is the object that handles talking to the Datadog API. This maintains +// state information for a particular application connection. +type Client struct { + apiKey, appKey string + + //The Http Client that is used to make requests + HttpClient *http.Client +} + +// NewClient returns a new datadog.Client which can be used to access the API +// methods. The expected argument is the API key. +func NewClient(apiKey, appKey string) *Client { + return &Client{ + apiKey: apiKey, + appKey: appKey, + HttpClient: http.DefaultClient, + } +} diff --git a/vendor/github.com/zorkian/go-datadog-api/monitors.go b/vendor/github.com/zorkian/go-datadog-api/monitors.go new file mode 100644 index 000000000..a9cae660f --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/monitors.go @@ -0,0 +1,113 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +import ( + "encoding/json" + "fmt" +) + +type ThresholdCount struct { + Ok json.Number `json:"ok,omitempty"` + Critical json.Number `json:"critical,omitempty"` + Warning json.Number `json:"warning,omitempty"` +} + +type Options struct { + NoDataTimeframe int `json:"no_data_timeframe,omitempty"` + NotifyAudit bool `json:"notify_audit,omitempty"` + NotifyNoData bool `json:"notify_no_data,omitempty"` + RenotifyInterval int `json:"renotify_interval,omitempty"` + Silenced map[string]int `json:"silenced,omitempty"` + TimeoutH int `json:"timeout_h,omitempty"` + EscalationMessage string `json:"escalation_message,omitempty"` + Thresholds ThresholdCount `json:"thresholds,omitempty"` + IncludeTags bool `json:"include_tags,omitempty"` +} + +//Monitors allow you to watch a metric or check that you care about, +//notifying your team when some defined threshold is exceeded. +type Monitor struct { + Id int `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Query string `json:"query,omitempty"` + Name string `json:"name,omitempty"` + Message string `json:"message,omitempty"` + Tags []string `json:"tags,omitempty"` + Options Options `json:"options,omitempty"` +} + +// reqMonitors receives a slice of all monitors +type reqMonitors struct { + Monitors []Monitor `json:"monitors,omitempty"` +} + +// Createmonitor adds a new monitor to the system. This returns a pointer to an +// monitor so you can pass that to Updatemonitor later if needed. +func (self *Client) CreateMonitor(monitor *Monitor) (*Monitor, error) { + var out Monitor + err := self.doJsonRequest("POST", "/v1/monitor", monitor, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +// Updatemonitor takes an monitor that was previously retrieved through some method +// and sends it back to the server. +func (self *Client) UpdateMonitor(monitor *Monitor) error { + return self.doJsonRequest("PUT", fmt.Sprintf("/v1/monitor/%d", monitor.Id), + monitor, nil) +} + +// Getmonitor retrieves an monitor by identifier. +func (self *Client) GetMonitor(id int) (*Monitor, error) { + var out Monitor + err := self.doJsonRequest("GET", fmt.Sprintf("/v1/monitor/%d", id), nil, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +// Deletemonitor removes an monitor from the system. +func (self *Client) DeleteMonitor(id int) error { + return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/monitor/%d", id), + nil, nil) +} + +// GetMonitors returns a slice of all monitors. +func (self *Client) GetMonitors() ([]Monitor, error) { + var out reqMonitors + err := self.doJsonRequest("GET", "/v1/monitor", nil, &out.Monitors) + if err != nil { + return nil, err + } + return out.Monitors, nil +} + +// MuteMonitors turns off monitoring notifications. +func (self *Client) MuteMonitors() error { + return self.doJsonRequest("POST", "/v1/monitor/mute_all", nil, nil) +} + +// UnmuteMonitors turns on monitoring notifications. +func (self *Client) UnmuteMonitors() error { + return self.doJsonRequest("POST", "/v1/monitor/unmute_all", nil, nil) +} + +// MuteMonitor turns off monitoring notifications for a monitor. +func (self *Client) MuteMonitor(id int) error { + return self.doJsonRequest("POST", fmt.Sprintf("/v1/monitor/%d/mute", id), nil, nil) +} + +// UnmuteMonitor turns on monitoring notifications for a monitor. +func (self *Client) UnmuteMonitor(id int) error { + return self.doJsonRequest("POST", fmt.Sprintf("/v1/monitor/%d/unmute", id), nil, nil) +} diff --git a/vendor/github.com/zorkian/go-datadog-api/request.go b/vendor/github.com/zorkian/go-datadog-api/request.go new file mode 100644 index 000000000..d161914f6 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/request.go @@ -0,0 +1,131 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "strings" + "time" + + "github.com/cenkalti/backoff" +) + +// uriForAPI is to be called with something like "/v1/events" and it will give +// the proper request URI to be posted to. +func (self *Client) uriForAPI(api string) string { + url := os.Getenv("DATADOG_HOST") + if url == "" { + url = "https://app.datadoghq.com" + } + if strings.Index(api, "?") > -1 { + return url + "/api" + api + "&api_key=" + + self.apiKey + "&application_key=" + self.appKey + } else { + return url + "/api" + api + "?api_key=" + + self.apiKey + "&application_key=" + self.appKey + } +} + +// doJsonRequest is the simplest type of request: a method on a URI that returns +// some JSON result which we unmarshal into the passed interface. +func (self *Client) doJsonRequest(method, api string, + reqbody, out interface{}) error { + // Handle the body if they gave us one. + var bodyreader io.Reader + if method != "GET" && reqbody != nil { + bjson, err := json.Marshal(reqbody) + if err != nil { + return err + } + bodyreader = bytes.NewReader(bjson) + } + + req, err := http.NewRequest(method, self.uriForAPI(api), bodyreader) + if err != nil { + return err + } + if bodyreader != nil { + req.Header.Add("Content-Type", "application/json") + } + + // Perform the request and retry it if it's not a POST request + var resp *http.Response + if method == "POST" { + resp, err = self.HttpClient.Do(req) + } else { + resp, err = self.doRequestWithRetries(req, 60*time.Second) + } + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + return fmt.Errorf("API error %s: %s", resp.Status, body) + } + + // If they don't care about the body, then we don't care to give them one, + // so bail out because we're done. + if out == nil { + return nil + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + // If we got no body, by default let's just make an empty JSON dict. This + // saves us some work in other parts of the code. + if len(body) == 0 { + body = []byte{'{', '}'} + } + + err = json.Unmarshal(body, &out) + if err != nil { + return err + } + return nil +} + +// doRequestWithRetries performs an HTTP request repeatedly for maxTime or until +// no error and no HTTP response code higher than 299 is returned. +func (self *Client) doRequestWithRetries(req *http.Request, maxTime time.Duration) (*http.Response, error) { + var ( + err error + resp *http.Response + bo = backoff.NewExponentialBackOff() + ) + bo.MaxElapsedTime = maxTime + + err = backoff.Retry(func() error { + resp, err = self.HttpClient.Do(req) + if err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return errors.New("API error: " + resp.Status) + } + return nil + }, bo) + + return resp, err +} diff --git a/vendor/github.com/zorkian/go-datadog-api/screen_widgets.go b/vendor/github.com/zorkian/go-datadog-api/screen_widgets.go new file mode 100644 index 000000000..69e159abe --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/screen_widgets.go @@ -0,0 +1,287 @@ +package datadog + +type TextSize struct { + Size int + Auto bool +} + +type TileDef struct { + Events []TileDefEvent `json:"events,omitempty"` + Markers []TimeseriesMarker `json:"markers,omitempty"` + Requests []TimeseriesRequest `json:"requests,omitempty"` + Viz string `json:"viz,omitempty"` +} + +type TimeseriesRequest struct { + Query string `json:"q,omitempty"` + Type string `json:"type,omitempty"` + ConditionalFormats []ConditionalFormat `json:"conditional_formats,omitempty"` + Style TimeseriesRequestStyle `json:"style,omitempty"` +} + +type TimeseriesRequestStyle struct { + Palette string `json:"palette,omitempty"` +} + +type TimeseriesMarker struct { + Label string `json:"label,omitempty"` + Type string `json:"type,omitempty"` + Value string `json:"value,omitempty"` +} + +type TileDefEvent struct { + Query string `json:"q"` +} + +type AlertValueWidget struct { + TitleSize int `json:"title_size,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TextAlign string `json:"text_align,omitempty"` + TitleText string `json:"title_text,omitempty"` + Precision int `json:"precision,omitempty"` + AlertId int `json:"alert_id,omitempty"` + Timeframe string `json:"timeframe,omitempty"` + AddTimeframe bool `json:"add_timeframe,omitempty"` + Y int `json:"y,omitempty"` + X int `json:"x,omitempty"` + TextSize string `json:"text_size,omitempty"` + Height int `json:"height,omitempty"` + Width int `json:"width,omitempty"` + Type string `json:"type,omitempty"` + Unit string `json:"unit,omitempty"` +} + +type ChangeWidget struct { + TitleSize int `json:"title_size,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TitleText string `json:"title_text,omitempty"` + Height int `json:"height,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"y,omitempty"` + Y int `json:"x,omitempty"` + Aggregator string `json:"aggregator,omitempty"` + TileDef TileDef `json:"tile_def,omitempty"` +} + +type GraphWidget struct { + TitleSize int `json:"title_size,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TitleText string `json:"title_text,omitempty"` + Height int `json:"height,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"y,omitempty"` + Y int `json:"x,omitempty"` + Type string `json:"type,omitempty"` + Timeframe string `json:"timeframe,omitempty"` + LegendSize int `json:"legend_size,omitempty"` + Legend bool `json:"legend,omitempty"` + TileDef TileDef `json:"tile_def,omitempty"` +} + +type EventTimelineWidget struct { + TitleSize int `json:"title_size,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TitleText string `json:"title_text,omitempty"` + Height int `json:"height,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"y,omitempty"` + Y int `json:"x,omitempty"` + Type string `json:"type,omitempty"` + Timeframe string `json:"timeframe,omitempty"` + Query string `json:"query,omitempty"` +} + +type AlertGraphWidget struct { + TitleSize int `json:"title_size,omitempty"` + VizType string `json:"timeseries,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TitleText string `json:"title_text,omitempty"` + Height int `json:"height,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"y,omitempty"` + Y int `json:"x,omitempty"` + AlertId int `json:"alert_id,omitempty"` + Timeframe string `json:"timeframe,omitempty"` + Type string `json:"type,omitempty"` + AddTimeframe bool `json:"add_timeframe,omitempty"` +} + +type HostMapWidget struct { + TitleSize int `json:"title_size,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TitleText string `json:"title_text,omitempty"` + Height int `json:"height,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"y,omitempty"` + Y int `json:"x,omitempty"` + Query string `json:"query,omitempty"` + Timeframe string `json:"timeframe,omitempty"` + LegendSize int `json:"legend_size,omitempty"` + Type string `json:"type,omitempty"` + Legend bool `json:"legend,omitempty"` + TileDef TileDef `json:"tile_def,omitempty"` +} + +type CheckStatusWidget struct { + TitleSize int `json:"title_size,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TextAlign string `json:"text_align,omitempty"` + TitleText string `json:"title_text,omitempty"` + Height int `json:"height,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"y,omitempty"` + Y int `json:"x,omitempty"` + Tags string `json:"tags,omitempty"` + Timeframe string `json:"timeframe,omitempty"` + TextSize string `json:"text_size,omitempty"` + Type string `json:"type,omitempty"` + Check string `json:"check,omitempty"` + Group string `json:"group,omitempty"` + Grouping string `json:"grouping,omitempty"` +} + +type IFrameWidget struct { + TitleSize int `json:"title_size,omitempty"` + Title bool `json:"title,omitempty"` + Url string `json:"url,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TitleText string `json:"title_text,omitempty"` + Height int `json:"height,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"y,omitempty"` + Y int `json:"x,omitempty"` + Type string `json:"type,omitempty"` +} + +type NoteWidget struct { + TitleSize int `json:"title_size,omitempty"` + Title bool `json:"title,omitempty"` + RefreshEvery int `json:"refresh_every,omitempty"` + TickPos string `json:"tick_pos,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TickEdge string `json:"tick_edge,omitempty"` + TextAlign string `json:"text_align,omitempty"` + TitleText string `json:"title_text,omitempty"` + Height int `json:"height,omitempty"` + Color string `json:"bgcolor,omitempty"` + Html string `json:"html,omitempty"` + Y int `json:"y,omitempty"` + X int `json:"x,omitempty"` + FontSize int `json:"font_size,omitempty"` + Tick bool `json:"tick,omitempty"` + Note string `json:"type,omitempty"` + Width int `json:"width,omitempty"` + AutoRefresh bool `json:"auto_refresh,omitempty"` +} + +type TimeseriesWidget struct { + Height int `json:"height,omitempty"` + Legend bool `json:"legend,omitempty"` + TileDef TileDef `json:"tile_def,omitempty"` + Timeframe string `json:"timeframe,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TitleSize TextSize `json:"title_size,omitempty"` + TitleText string `json:"title_text,omitempty"` + Type string `json:"type,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"x,omitempty"` + Y int `json:"y,omitempty"` +} + +type QueryValueWidget struct { + Timeframe string `json:"timeframe,omitempty"` + TimeframeAggregator string `json:"aggr,omitempty"` + Aggregator string `json:"aggregator,omitempty"` + CalcFunc string `json:"calc_func,omitempty"` + ConditionalFormats []ConditionalFormat `json:"conditional_formats,omitempty"` + Height int `json:"height,omitempty"` + IsValidQuery bool `json:"is_valid_query,omitempty,omitempty"` + Metric string `json:"metric,omitempty"` + MetricType string `json:"metric_type,omitempty"` + Precision int `json:"precision,omitempty"` + Query string `json:"query,omitempty"` + ResultCalcFunc string `json:"res_calc_func,omitempty"` + Tags []string `json:"tags,omitempty"` + TextAlign string `json:"text_align,omitempty"` + TextSize TextSize `json:"text_size,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TitleSize TextSize `json:"title_size,omitempty"` + TitleText string `json:"title_text,omitempty"` + Type string `json:"type,omitempty"` + Unit string `json:"auto,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"x,omitempty"` + Y int `json:"y,omitempty"` +} +type ConditionalFormat struct { + Color string `json:"color,omitempty"` + Comparator string `json:"comparator,omitempty"` + Inverted bool `json:"invert,omitempty"` + Value int `json:"value,omitempty"` +} + +type ToplistWidget struct { + Height int `json:"height,omitempty"` + Legend bool `json:"legend,omitempty"` + LegendSize int `json:"legend_size,omitempty"` + TileDef TileDef `json:"tile_def,omitempty"` + Timeframe string `json:"timeframe,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TitleSize TextSize `json:"title_size,omitempty"` + TitleText string `json:"title_text,omitempty"` + Type string `json:"type,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"x,omitempty"` + Y int `json:"y,omitempty"` +} + +type EventStreamWidget struct { + EventSize string `json:"event_size,omitempty"` + Height int `json:"height,omitempty"` + Query string `json:"query,omitempty"` + Timeframe string `json:"timeframe,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TitleSize TextSize `json:"title_size,omitempty"` + TitleText string `json:"title_text,omitempty"` + Type string `json:"type,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"x,omitempty"` + Y int `json:"y,omitempty"` +} + +type FreeTextWidget struct { + Color string `json:"color,omitempty"` + FontSize string `json:"font_size,omitempty"` + Height int `json:"height,omitempty"` + Text string `json:"text,omitempty"` + TextAlign string `json:"text_align,omitempty"` + Type string `json:"type,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"x,omitempty"` + Y int `json:"y,omitempty"` +} + +type ImageWidget struct { + Height int `json:"height,omitempty"` + Sizing string `json:"sizing,omitempty"` + Title bool `json:"title,omitempty"` + TitleAlign string `json:"title_align,omitempty"` + TitleSize TextSize `json:"title_size,omitempty"` + TitleText string `json:"title_text,omitempty"` + Type string `json:"type,omitempty"` + Url string `json:"url,omitempty"` + Width int `json:"width,omitempty"` + X int `json:"x,omitempty"` + Y int `json:"y,omitempty"` +} diff --git a/vendor/github.com/zorkian/go-datadog-api/screenboards.go b/vendor/github.com/zorkian/go-datadog-api/screenboards.go new file mode 100644 index 000000000..2317e392b --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/screenboards.go @@ -0,0 +1,117 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +import ( + "fmt" +) + +// Screenboard represents a user created screenboard. This is the full screenboard +// struct when we load a screenboard in detail. +type Screenboard struct { + Id int `json:"id,omitempty"` + Title string `json:"board_title,omitempty"` + Height string `json:"height,omitempty"` + Width string `json:"width,omitempty"` + Shared bool `json:"shared,omitempty"` + Templated bool `json:"templated,omitempty"` + TemplateVariables []TemplateVariable `json:"template_variables,omitempty"` + Widgets []Widget `json:"widgets,omitempty"` +} + +//type Widget struct { +type Widget struct { + Default string `json:"default,omitempty"` + Name string `json:"name,omitempty"` + Prefix string `json:"prefix,omitempty"` + TimeseriesWidget TimeseriesWidget `json:"timeseries,omitempty"` + QueryValueWidget QueryValueWidget `json:"query_value,omitempty"` + EventStreamWidget EventStreamWidget `json:"event_stream,omitempty"` + FreeTextWidget FreeTextWidget `json:"free_text,omitempty"` + ToplistWidget ToplistWidget `json:"toplist,omitempty"` + ImageWidget ImageWidget `json:"image,omitempty"` + ChangeWidget ChangeWidget `json:"change,omitempty"` + GraphWidget GraphWidget `json:"graph,omitempty"` + EventTimelineWidget EventTimelineWidget `json:"event_timeline,omitempty"` + AlertValueWidget AlertValueWidget `json:"alert_value,omitempty"` + AlertGraphWidget AlertGraphWidget `json:"alert_graph,omitempty"` + HostMapWidget HostMapWidget `json:"hostmap,omitempty"` + CheckStatusWidget CheckStatusWidget `json:"check_status,omitempty"` + IFrameWidget IFrameWidget `json:"iframe,omitempty"` + NoteWidget NoteWidget `json:"frame,omitempty"` +} + +// ScreenboardLite represents a user created screenboard. This is the mini +// struct when we load the summaries. +type ScreenboardLite struct { + Id int `json:"id,omitempty"` + Resource string `json:"resource,omitempty"` + Title string `json:"title,omitempty"` +} + +// reqGetScreenboards from /api/v1/screen +type reqGetScreenboards struct { + Screenboards []*ScreenboardLite `json:"screenboards"` +} + +// GetScreenboard returns a single screenboard created on this account. +func (self *Client) GetScreenboard(id int) (*Screenboard, error) { + out := &Screenboard{} + err := self.doJsonRequest("GET", fmt.Sprintf("/v1/screen/%d", id), nil, out) + if err != nil { + return nil, err + } + return out, nil +} + +// GetScreenboards returns a list of all screenboards created on this account. +func (self *Client) GetScreenboards() ([]*ScreenboardLite, error) { + var out reqGetScreenboards + err := self.doJsonRequest("GET", "/v1/screen", nil, &out) + if err != nil { + return nil, err + } + return out.Screenboards, nil +} + +// DeleteScreenboard deletes a screenboard by the identifier. +func (self *Client) DeleteScreenboard(id int) error { + return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/screen/%d", id), nil, nil) +} + +// CreateScreenboard creates a new screenboard when given a Screenboard struct. Note +// that the Id, Resource, Url and similar elements are not used in creation. +func (self *Client) CreateScreenboard(board *Screenboard) (*Screenboard, error) { + out := &Screenboard{} + if err := self.doJsonRequest("POST", "/v1/screen", board, out); err != nil { + return nil, err + } + return out, nil +} + +// UpdateScreenboard in essence takes a Screenboard struct and persists it back to +// the server. Use this if you've updated your local and need to push it back. +func (self *Client) UpdateScreenboard(board *Screenboard) error { + return self.doJsonRequest("PUT", fmt.Sprintf("/v1/screen/%d", board.Id), board, nil) +} + +type ScreenShareResponse struct { + BoardId int `json:"board_id"` + PublicUrl string `json:"public_url"` +} + +// ShareScreenboard shares an existing screenboard, it takes and updates ScreenShareResponse +func (self *Client) ShareScreenboard(id int, response *ScreenShareResponse) error { + return self.doJsonRequest("GET", fmt.Sprintf("/v1/screen/share/%d", id), nil, response) +} + +// RevokeScreenboard revokes a currently shared screenboard +func (self *Client) RevokeScreenboard(id int) error { + return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/screen/share/%d", id), nil, nil) +} diff --git a/vendor/github.com/zorkian/go-datadog-api/search.go b/vendor/github.com/zorkian/go-datadog-api/search.go new file mode 100644 index 000000000..b5e83efde --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/search.go @@ -0,0 +1,37 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +// reqSearch is the container for receiving search results. +type reqSearch struct { + Results struct { + Hosts []string `json:"hosts,omitempty"` + Metrics []string `json:"metrics,omitempty"` + } `json:"results"` +} + +// SearchHosts searches through the hosts facet, returning matching hostnames. +func (self *Client) SearchHosts(search string) ([]string, error) { + var out reqSearch + err := self.doJsonRequest("GET", "/v1/search?q=hosts:"+search, nil, &out) + if err != nil { + return nil, err + } + return out.Results.Hosts, nil +} + +// SearchMetrics searches through the metrics facet, returning matching ones. +func (self *Client) SearchMetrics(search string) ([]string, error) { + var out reqSearch + err := self.doJsonRequest("GET", "/v1/search?q=metrics:"+search, nil, &out) + if err != nil { + return nil, err + } + return out.Results.Metrics, nil +} diff --git a/vendor/github.com/zorkian/go-datadog-api/series.go b/vendor/github.com/zorkian/go-datadog-api/series.go new file mode 100644 index 000000000..1bfe0efd1 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/series.go @@ -0,0 +1,68 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +import "strconv" + +// DataPoint is a tuple of [UNIX timestamp, value]. This has to use floats +// because the value could be non-integer. +type DataPoint [2]float64 + +// Metric represents a collection of data points that we might send or receive +// on one single metric line. +type Metric struct { + Metric string `json:"metric,omitempty"` + Points []DataPoint `json:"points,omitempty"` + Type string `json:"type,omitempty"` + Host string `json:"host,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +// Series represents a collection of data points we get when we query for timeseries data +type Series struct { + Metric string `json:"metric,omitempty"` + DisplayName string `json:"display_name,omitempty"` + Points []DataPoint `json:"pointlist,omitempty"` + Start float64 `json:"start,omitempty"` + End float64 `json:"end,omitempty"` + Interval int `json:"interval,omitempty"` + Aggr string `json:"aggr,omitempty"` + Length int `json:"length,omitempty"` + Scope string `json:"scope,omitempty"` + Expression string `json:"expression,omitempty"` +} + +// reqPostSeries from /api/v1/series +type reqPostSeries struct { + Series []Metric `json:"series,omitempty"` +} + +// reqMetrics is the container for receiving metric results. +type reqMetrics struct { + Series []Series `json:"series,omitempty"` +} + +// PostMetrics takes as input a slice of metrics and then posts them up to the +// server for posting data. +func (client *Client) PostMetrics(series []Metric) error { + return client.doJsonRequest("POST", "/v1/series", + reqPostSeries{Series: series}, nil) +} + +// QueryMetrics takes as input from, to (seconds from Unix Epoch) and query string and then requests +// timeseries data for that time peried +func (client *Client) QueryMetrics(from, to int64, query string) ([]Series, error) { + var out reqMetrics + err := client.doJsonRequest("GET", "/v1/query?from="+strconv.FormatInt(from, 10)+"&to="+strconv.FormatInt(to, 10)+"&query="+query, + nil, &out) + if err != nil { + return nil, err + } + return out.Series, nil +} diff --git a/vendor/github.com/zorkian/go-datadog-api/snapshot.go b/vendor/github.com/zorkian/go-datadog-api/snapshot.go new file mode 100644 index 000000000..145a19bcc --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/snapshot.go @@ -0,0 +1,33 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2016 by authors and contributors. + */ + +package datadog + +import ( + "fmt" + "net/url" + "time" +) + +// Snapshot creates an image from a graph and returns the URL of the image. +func (self *Client) Snapshot(query string, start, end time.Time, eventQuery string) (string, error) { + v := url.Values{} + v.Add("start", fmt.Sprintf("%d", start.Unix())) + v.Add("end", fmt.Sprintf("%d", end.Unix())) + v.Add("metric_query", query) + v.Add("event_query", eventQuery) + + out := struct { + SnapshotURL string `json:"snapshot_url"` + }{} + err := self.doJsonRequest("GET", "/v1/graph/snapshot?"+v.Encode(), nil, &out) + if err != nil { + return "", err + } + return out.SnapshotURL, nil +} diff --git a/vendor/github.com/zorkian/go-datadog-api/tags.go b/vendor/github.com/zorkian/go-datadog-api/tags.go new file mode 100644 index 000000000..20043d1ae --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/tags.go @@ -0,0 +1,96 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +// TagMap is used to receive the format given to us by the API. +type TagMap map[string][]string + +// reqGetTags is the container for receiving tags. +type reqGetTags struct { + Tags TagMap `json:"tags,omitempty"` +} + +// regGetHostTags is for receiving a slice of tags. +type reqGetHostTags struct { + Tags []string `json:"tags,omitempty"` +} + +// GetTags returns a map of tags. +func (self *Client) GetTags(source string) (TagMap, error) { + var out reqGetTags + uri := "/v1/tags/hosts" + if source != "" { + uri += "?source=" + source + } + err := self.doJsonRequest("GET", uri, nil, &out) + if err != nil { + return nil, err + } + return out.Tags, nil +} + +// GetHostTags returns a slice of tags for a given host and source. +func (self *Client) GetHostTags(host, source string) ([]string, error) { + var out reqGetHostTags + uri := "/v1/tags/hosts/" + host + if source != "" { + uri += "?source=" + source + } + err := self.doJsonRequest("GET", uri, nil, &out) + if err != nil { + return nil, err + } + return out.Tags, nil +} + +// GetHostTagsBySource is a different way of viewing the tags. It returns a map +// of source:[tag,tag]. +func (self *Client) GetHostTagsBySource(host, source string) (TagMap, error) { + var out reqGetTags + uri := "/v1/tags/hosts/" + host + "?by_source=true" + if source != "" { + uri += "&source=" + source + } + err := self.doJsonRequest("GET", uri, nil, &out) + if err != nil { + return nil, err + } + return out.Tags, nil +} + +// AddTagsToHost does exactly what it says on the tin. Given a list of tags, +// add them to the host. The source is optionally specificed, and defaults to +// "users" as per the API documentation. +func (self *Client) AddTagsToHost(host, source string, tags []string) error { + uri := "/v1/tags/hosts/" + host + if source != "" { + uri += "?source=" + source + } + return self.doJsonRequest("POST", uri, reqGetHostTags{Tags: tags}, nil) +} + +// UpdateHostTags overwrites existing tags for a host, allowing you to specify +// a new set of tags for the given source. This defaults to "users". +func (self *Client) UpdateHostTags(host, source string, tags []string) error { + uri := "/v1/tags/hosts/" + host + if source != "" { + uri += "?source=" + source + } + return self.doJsonRequest("PUT", uri, reqGetHostTags{Tags: tags}, nil) +} + +// RemoveHostTags removes all tags from a host for the given source. If none is +// given, the API defaults to "users". +func (self *Client) RemoveHostTags(host, source string) error { + uri := "/v1/tags/hosts/" + host + if source != "" { + uri += "?source=" + source + } + return self.doJsonRequest("DELETE", uri, nil, nil) +} diff --git a/vendor/github.com/zorkian/go-datadog-api/users.go b/vendor/github.com/zorkian/go-datadog-api/users.go new file mode 100644 index 000000000..76b4da573 --- /dev/null +++ b/vendor/github.com/zorkian/go-datadog-api/users.go @@ -0,0 +1,71 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +type User struct { + Handle string `json:"handle,omitempty"` + Email string `json:"email,omitempty"` + Name string `json:"name,omitempty"` + Role string `json:"role,omitempty"` + IsAdmin bool `json:"is_admin,omitempty"` + Verified bool `json:"verified,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +// reqInviteUsers contains email addresses to send invitations to. +type reqInviteUsers struct { + Emails []string `json:"emails,omitempty"` +} + +// InviteUsers takes a slice of email addresses and sends invitations to them. +func (self *Client) InviteUsers(emails []string) error { + return self.doJsonRequest("POST", "/v1/invite_users", + reqInviteUsers{Emails: emails}, nil) +} + +// internal type to retrieve users from the api +type usersData struct { + Users []User `json:"users"` +} + +// GetUsers returns all user, or an error if not found +func (self *Client) GetUsers() (users []User, err error) { + var udata usersData + uri := "/v1/user" + err = self.doJsonRequest("GET", uri, nil, &udata) + users = udata.Users + return +} + +// internal type to retrieve single user from the api +type userData struct { + User User `json:"user"` +} + +// GetUser returns the user that match a handle, or an error if not found +func (self *Client) GetUser(handle string) (user User, err error) { + var udata userData + uri := "/v1/user/" + handle + err = self.doJsonRequest("GET", uri, nil, &udata) + user = udata.User + return +} + +// UpdateUser updates a user with the content of `user`, +// and returns an error if the update failed +func (self *Client) UpdateUser(user User) error { + uri := "/v1/user/" + user.Handle + return self.doJsonRequest("PUT", uri, user, nil) +} + +// DeleteUser deletes a user and returns an error if deletion failed +func (self *Client) DeleteUser(handle string) error { + uri := "/v1/user/" + handle + return self.doJsonRequest("DELETE", uri, nil, nil) +} diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index 9737e0f53..c1a6e629d 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -14,6 +14,7 @@ body.layout-azurerm, body.layout-cloudflare, body.layout-cloudstack, body.layout-consul, +body.layout-datadog, body.layout-digitalocean, body.layout-dme, body.layout-dnsimple, diff --git a/website/source/docs/providers/datadog/index.html.markdown b/website/source/docs/providers/datadog/index.html.markdown new file mode 100644 index 000000000..9e84050fe --- /dev/null +++ b/website/source/docs/providers/datadog/index.html.markdown @@ -0,0 +1,38 @@ +--- +layout: "datadog" +page_title: "Provider: Datadog" +sidebar_current: "docs-datadog-index" +description: |- + The Datadog provider is used to interact with the resources supported by Datadog. The provider needs to be configured with the proper credentials before it can be used. +--- + +# Datadog Provider + +The [Datadog](https://www.datadoghq.com) provider is used to interact with the +resources supported by Datadog. The provider needs to be configured +with the proper credentials before it can be used. + +Use the navigation to the left to read about the available resources. + +## Example Usage + +``` +# Configure the Datadog provider +provider "datadog" { + api_key = "${var.datadog_api_key}" + app_key = "${var.datadog_app_key}" +} + +# Create a new monitor +resource "datadog_monitor" "default" { + ... +} +``` + +## Argument Reference + +The following arguments are supported: + +* `api_key` - (Required) Datadog API key +* `app_key` - (Required) Datadog APP key + diff --git a/website/source/docs/providers/datadog/r/monitor.html.markdown b/website/source/docs/providers/datadog/r/monitor.html.markdown new file mode 100644 index 000000000..e0032888c --- /dev/null +++ b/website/source/docs/providers/datadog/r/monitor.html.markdown @@ -0,0 +1,94 @@ +--- +layout: "datadog" +page_title: "Datadog: datadog_monitor" +sidebar_current: "docs-datadog-resource-monitor" +description: |- + Provides a Datadog monitor resource. This can be used to create and manage monitors. +--- + +# datadog\_monitor + +Provides a Datadog monitor resource. This can be used to create and manage Datadog monitors. + +## Example Usage + +``` +# Create a new Datadog monitor +resource "datadog_monitor" "foo" { + name = "Name for monitor foo" + type = "Metric alert" + message = "Monitor triggered. Notify: @hipchat-channel" + escalation_message = "Escalation message @pagerduty" + + query = "avg(last_1h):avg:aws.ec2.cpu{environment:foo,host:foo} by {host} > 2" + + thresholds { + ok = 0 + warning = 1 + critical = 2 + } + + notify_no_data = false + renotify_interval = 60 + + notify_audit = false + timeout_h = 60 + include_tags = true + silenced { + "*" = 0 + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `type` - (Required) The type of the monitor, chosen from: + * `metric alert` + * `service check` + * `event alert` + * `query alert` +* `name` - (Required) Name of Datadog monitor +* `query` - (Required) The monitor query to notify on with syntax varying depending on what type of monitor + you are creating. See [API Reference](http://docs.datadoghq.com/api) for options. +* `message` - (Required) A message to include with notifications for this monitor. + Email notifications can be sent to specific users by using the same '@username' notation as events. +* `escalation_message` - (Optional) A message to include with a re-notification. Supports the '@username' + notification allowed elsewhere. +* `thresholds` - (Required) Thresholds by threshold type: + * `ok` + * `warning` + * `critical` +* `notify_no_data` (Optional) A boolean indicating whether this monitor will notify when data stops reporting. Defaults + to false. +* `no_data_timeframe` (Optional) The number of minutes before a monitor will notify when data stops reporting. Must be at + least 2x the monitor timeframe for metric alerts or 2 minutes for service checks. Default: 2x timeframe for + metric alerts, 2 minutes for service checks. +* `renotify_interval` (Optional) The number of minutes after the last notification before a monitor will re-notify + on the current status. It will only re-notify if it's not resolved. +* `notify_audit` (Optional) A boolean indicating whether tagged users will be notified on changes to this monitor. + Defaults to false. +* `timeout_h` (Optional) The number of hours of the monitor not reporting data before it will automatically resolve + from a triggered state. Defaults to false. +* `include_tags` (Optional) A boolean indicating whether notifications from this monitor will automatically insert its + triggering tags into the title. Defaults to true. +* `silenced` (Optional) Each scope will be muted until the given POSIX timestamp or forever if the value is 0. + + To mute the alert completely: + + silenced { + '*' = 0 + } + + To mute role:db for a short time: + + silenced { + 'role:db' = 1412798116 + } + +## Attributes Reference + +The following attributes are exported: + +* `id` - ID of the Datadog monitor diff --git a/website/source/layouts/datadog.erb b/website/source/layouts/datadog.erb new file mode 100644 index 000000000..77f816429 --- /dev/null +++ b/website/source/layouts/datadog.erb @@ -0,0 +1,26 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> + <% end %> diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 53b810b19..a000ec6c8 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -158,6 +158,10 @@ Consul + > + Datadog + + > DigitalOcean