provider/datadog: Replace separate monitors with one generic monitor with read functionality.

This commit is contained in:
Otto Jongerius 2016-02-01 22:39:02 +11:00 committed by James Nugent
parent 775a3f8826
commit eb2407fccf
11 changed files with 535 additions and 1088 deletions

View File

@ -7,7 +7,6 @@ import (
"github.com/hashicorp/terraform/terraform"
)
// Provider returns a terraform.ResourceProvider.
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
@ -24,16 +23,13 @@ func Provider() terraform.ResourceProvider {
},
ResourcesMap: map[string]*schema.Resource{
"datadog_service_check": resourceDatadogServiceCheck(),
"datadog_metric_alert": resourceDatadogMetricAlert(),
"datadog_outlier_alert": resourceDatadogOutlierAlert(),
"datadog_monitor": resourceDatadogMonitor(),
},
ConfigureFunc: providerConfigure,
}
}
// ProviderConfigure returns a configured client.
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{

View File

@ -1,201 +0,0 @@
package datadog
import (
"bytes"
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
"github.com/zorkian/go-datadog-api"
)
// resourceDatadogMetricAlert is a Datadog monitor resource
func resourceDatadogMetricAlert() *schema.Resource {
return &schema.Resource{
Create: resourceDatadogMetricAlertCreate,
Read: resourceDatadogGenericRead,
Update: resourceDatadogMetricAlertUpdate,
Delete: resourceDatadogGenericDelete,
Exists: resourceDatadogGenericExists,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"metric": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"query"},
},
"tags": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"keys": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
ConflictsWith: []string{"query"},
},
"time_aggr": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"query"},
},
"time_window": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"query"},
},
"space_aggr": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"query"},
},
"operator": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"message": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
// Optional Query for custom monitors
"query": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"time_aggr", "time_window", "space_aggr", "metric", "keys"},
},
"thresholds": thresholdSchema(),
// Additional Settings
"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,
Default: 0,
},
},
}
}
// buildMonitorStruct returns a monitor struct
func buildMetricAlertStruct(d *schema.ResourceData) *datadog.Monitor {
name := d.Get("name").(string)
message := d.Get("message").(string)
timeAggr := d.Get("time_aggr").(string)
timeWindow := d.Get("time_window").(string)
spaceAggr := d.Get("space_aggr").(string)
metric := d.Get("metric").(string)
query := d.Get("query").(string)
// Tags are are no separate resource/gettable, so some trickery is needed
var buffer bytes.Buffer
if raw, ok := d.GetOk("tags"); ok {
list := raw.([]interface{})
length := (len(list) - 1)
for i, v := range list {
buffer.WriteString(fmt.Sprintf("%s", v))
if i != length {
buffer.WriteString(",")
}
}
}
tagsParsed := buffer.String()
// Keys are used for multi alerts
var b bytes.Buffer
if raw, ok := d.GetOk("keys"); ok {
list := raw.([]interface{})
b.WriteString("by {")
length := (len(list) - 1)
for i, v := range list {
b.WriteString(fmt.Sprintf("%s", v))
if i != length {
b.WriteString(",")
}
}
b.WriteString("}")
}
keys := b.String()
threshold, thresholds := getThresholds(d)
operator := d.Get("operator").(string)
var q string
if query == "" {
q = fmt.Sprintf("%s(%s):%s:%s{%s} %s %s %s", timeAggr,
timeWindow,
spaceAggr,
metric,
tagsParsed,
keys,
operator,
threshold)
} else {
q = fmt.Sprintf("%s %s %s", query, operator, threshold)
}
log.Print(fmt.Sprintf("[DEBUG] submitting query: %s", q))
o := datadog.Options{
NotifyNoData: d.Get("notify_no_data").(bool),
NoDataTimeframe: d.Get("no_data_timeframe").(int),
RenotifyInterval: d.Get("renotify_interval").(int),
Thresholds: thresholds,
}
m := datadog.Monitor{
Type: "metric alert",
Query: q,
Name: name,
Message: message,
Options: o,
}
return &m
}
// resourceDatadogMetricAlertCreate creates a monitor.
func resourceDatadogMetricAlertCreate(d *schema.ResourceData, meta interface{}) error {
m := buildMetricAlertStruct(d)
if err := monitorCreator(d, meta, m); err != nil {
return err
}
return nil
}
// resourceDatadogMetricAlertUpdate updates a monitor.
func resourceDatadogMetricAlertUpdate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] running update.")
m := buildMetricAlertStruct(d)
if err := monitorUpdater(d, meta, m); err != nil {
return err
}
return nil
}

View File

@ -1,170 +0,0 @@
package datadog
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/zorkian/go-datadog-api"
)
func TestAccDatadogMetricAlert_Basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDatadogMetricAlertDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckDatadogMetricAlertConfigBasic,
Check: resource.ComposeTestCheckFunc(
testAccCheckDatadogMetricAlertExists("datadog_metric_alert.foo"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "name", "name for metric_alert foo"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "message", "{{#is_alert}}Metric alert foo is critical"+
"{{/is_alert}}\n{{#is_warning}}Metric alert foo is at warning "+
"level{{/is_warning}}\n{{#is_recovery}}Metric alert foo has "+
"recovered{{/is_recovery}}\nNotify: @hipchat-channel\n"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "metric", "aws.ec2.cpu"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "tags.0", "environment:foo"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "tags.1", "host:foo"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "tags.#", "2"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "keys.0", "host"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "keys.#", "1"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "time_aggr", "avg"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "time_window", "last_1h"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "space_aggr", "avg"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "operator", ">"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "notify_no_data", "false"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "renotify_interval", "60"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "thresholds.ok", "0"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "thresholds.warning", "1"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "thresholds.critical", "2"),
),
},
},
})
}
func TestAccDatadogMetricAlert_Query(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDatadogMetricAlertDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckDatadogMetricAlertConfigQuery,
Check: resource.ComposeTestCheckFunc(
testAccCheckDatadogMetricAlertExists("datadog_metric_alert.foo"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "name", "name for metric_alert foo"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "message", "{{#is_alert}}Metric alert foo is critical"+
"{{/is_alert}}\n{{#is_warning}}Metric alert foo is at warning "+
"level{{/is_warning}}\n{{#is_recovery}}Metric alert foo has "+
"recovered{{/is_recovery}}\nNotify: @hipchat-channel\n"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "query", "avg(last_1h):avg:aws.ec2.cpu{environment:foo,host:foo} by {host}"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "operator", ">"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "notify_no_data", "false"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "renotify_interval", "60"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "thresholds.ok", "0"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "thresholds.warning", "1"),
resource.TestCheckResourceAttr(
"datadog_metric_alert.foo", "thresholds.critical", "2"),
),
},
},
})
}
func testAccCheckDatadogMetricAlertDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*datadog.Client)
if err := destroyHelper(s, client); err != nil {
return err
}
return nil
}
func testAccCheckDatadogMetricAlertExists(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 testAccCheckDatadogMetricAlertConfigBasic = `
resource "datadog_metric_alert" "foo" {
name = "name for metric_alert foo"
message = <<EOF
{{#is_alert}}Metric alert foo is critical{{/is_alert}}
{{#is_warning}}Metric alert foo is at warning level{{/is_warning}}
{{#is_recovery}}Metric alert foo has recovered{{/is_recovery}}
Notify: @hipchat-channel
EOF
metric = "aws.ec2.cpu"
tags = ["environment:foo", "host:foo"]
keys = ["host"]
time_aggr = "avg" // avg, sum, max, min, change, or pct_change
time_window = "last_1h" // last_#m (5, 10, 15, 30), last_#h (1, 2, 4), or last_1d
space_aggr = "avg" // avg, sum, min, or max
operator = ">" // <, <=, >, >=, ==, or !=
thresholds {
ok = 0
warning = 1
critical = 2
}
notify_no_data = false
renotify_interval = 60
}
`
const testAccCheckDatadogMetricAlertConfigQuery = `
resource "datadog_metric_alert" "foo" {
name = "name for metric_alert foo"
message = <<EOF
{{#is_alert}}Metric alert foo is critical{{/is_alert}}
{{#is_warning}}Metric alert foo is at warning level{{/is_warning}}
{{#is_recovery}}Metric alert foo has recovered{{/is_recovery}}
Notify: @hipchat-channel
EOF
operator = ">" // <, <=, >, >=, ==, or !=
query = "avg(last_1h):avg:aws.ec2.cpu{environment:foo,host:foo} by {host}"
thresholds {
ok = 0
warning = 1
critical = 2
}
notify_no_data = false
renotify_interval = 60
}
`

View File

@ -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
}

View File

@ -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
}

View File

@ -1,184 +0,0 @@
package datadog
import (
"bytes"
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
"github.com/zorkian/go-datadog-api"
)
// resourceDatadogOutlierAlert is a Datadog monitor resource
func resourceDatadogOutlierAlert() *schema.Resource {
return &schema.Resource{
Create: resourceDatadogOutlierAlertCreate,
Read: resourceDatadogGenericRead,
Update: resourceDatadogOutlierAlertUpdate,
Delete: resourceDatadogGenericDelete,
Exists: resourceDatadogGenericExists,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"metric": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"tags": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"keys": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"time_aggr": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"time_window": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"space_aggr": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"message": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"threshold": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
// Additional Settings
"notify_no_data": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"no_data_timeframe": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"algorithm": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "dbscan",
},
"renotify_interval": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 0,
},
},
}
}
// buildMonitorStruct returns a monitor struct
func buildOutlierAlertStruct(d *schema.ResourceData) *datadog.Monitor {
name := d.Get("name").(string)
message := d.Get("message").(string)
timeAggr := d.Get("time_aggr").(string)
timeWindow := d.Get("time_window").(string)
spaceAggr := d.Get("space_aggr").(string)
metric := d.Get("metric").(string)
algorithm := d.Get("algorithm").(string)
// Tags are are no separate resource/gettable, so some trickery is needed
var buffer bytes.Buffer
if raw, ok := d.GetOk("tags"); ok {
list := raw.([]interface{})
length := (len(list) - 1)
for i, v := range list {
if length > 1 && v == "*" {
log.Print(fmt.Sprintf("[DEBUG] found wildcard, this is not supported for this type: %s", v))
continue
}
buffer.WriteString(fmt.Sprintf("%s", v))
if i != length {
buffer.WriteString(",")
}
}
}
tagsParsed := buffer.String()
// Keys are used for multi alerts
var b bytes.Buffer
if raw, ok := d.GetOk("keys"); ok {
list := raw.([]interface{})
b.WriteString("by {")
length := (len(list) - 1)
for i, v := range list {
b.WriteString(fmt.Sprintf("%s", v))
if i != length {
b.WriteString(",")
}
}
b.WriteString("}")
}
keys := b.String()
query := fmt.Sprintf("%s(%s):outliers(%s:%s{%s} %s, '%s',%s) > 0", timeAggr,
timeWindow,
spaceAggr,
metric,
tagsParsed,
keys,
algorithm,
d.Get("threshold"))
log.Print(fmt.Sprintf("[DEBUG] submitting query: %s", query))
o := datadog.Options{
NotifyNoData: d.Get("notify_no_data").(bool),
NoDataTimeframe: d.Get("no_data_timeframe").(int),
RenotifyInterval: d.Get("renotify_interval").(int),
}
m := datadog.Monitor{
Type: "query alert",
Query: query,
Name: name,
Message: message,
Options: o,
}
return &m
}
// resourceDatadogOutlierAlertCreate creates a monitor.
func resourceDatadogOutlierAlertCreate(d *schema.ResourceData, meta interface{}) error {
m := buildOutlierAlertStruct(d)
if err := monitorCreator(d, meta, m); err != nil {
return err
}
return nil
}
// resourceDatadogOutlierAlertUpdate updates a monitor.
func resourceDatadogOutlierAlertUpdate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] running update.")
m := buildOutlierAlertStruct(d)
if err := monitorUpdater(d, meta, m); err != nil {
return err
}
return nil
}

View File

@ -1,97 +0,0 @@
package datadog
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/zorkian/go-datadog-api"
)
func TestAccDatadogOutlierAlert_Basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDatadogOutlierAlertDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckDatadogOutlierAlertConfigBasic,
Check: resource.ComposeTestCheckFunc(
testAccCheckDatadogOutlierAlertExists("datadog_outlier_alert.foo"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "name", "name for outlier_alert foo"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "message", "description for outlier_alert foo @hipchat-name"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "metric", "system.load.5"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "tags.0", "environment:foo"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "tags.1", "host:foo"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "tags.#", "2"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "keys.0", "host"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "keys.#", "1"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "time_aggr", "avg"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "time_window", "last_1h"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "space_aggr", "avg"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "notify_no_data", "false"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "algorithm", "mad"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "renotify_interval", "60"),
resource.TestCheckResourceAttr(
"datadog_outlier_alert.foo", "threshold", "2"),
),
},
},
})
}
func testAccCheckDatadogOutlierAlertDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*datadog.Client)
if err := destroyHelper(s, client); err != nil {
return err
}
return nil
}
func testAccCheckDatadogOutlierAlertExists(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 testAccCheckDatadogOutlierAlertConfigBasic = `
resource "datadog_outlier_alert" "foo" {
name = "name for outlier_alert foo"
message = "description for outlier_alert foo @hipchat-name"
algorithm = "mad"
metric = "system.load.5"
tags = ["environment:foo", "host:foo"]
keys = ["host"]
time_aggr = "avg" // avg, sum, max, min, change, or pct_change
time_window = "last_1h" // last_#m (5, 10, 15, 30), last_#h (1, 2, 4), or last_1d
space_aggr = "avg" // avg, sum, min, or max
threshold = 2.0
notify_no_data = false
renotify_interval = 60
}
`

View File

@ -1,163 +0,0 @@
package datadog
import (
"bytes"
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
"github.com/zorkian/go-datadog-api"
)
// resourceDatadogServiceCheck is a Datadog monitor resource
func resourceDatadogServiceCheck() *schema.Resource {
return &schema.Resource{
Create: resourceDatadogServiceCheckCreate,
Read: resourceDatadogGenericRead,
Update: resourceDatadogServiceCheckUpdate,
Delete: resourceDatadogGenericDelete,
Exists: resourceDatadogGenericExists,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"check": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"thresholds": thresholdSchema(),
"tags": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"keys": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"message": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
// Additional Settings
"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,
Default: 0,
},
},
}
}
// buildServiceCheckStruct returns a monitor struct
func buildServiceCheckStruct(d *schema.ResourceData) *datadog.Monitor {
log.Print("[DEBUG] building monitor struct")
name := d.Get("name").(string)
message := d.Get("message").(string)
// Tags are are no separate resource/gettable, so some trickery is needed
var buffer bytes.Buffer
if raw, ok := d.GetOk("tags"); ok {
list := raw.([]interface{})
length := (len(list) - 1)
for i, v := range list {
buffer.WriteString(fmt.Sprintf("\"%s\"", v))
if i != length {
buffer.WriteString(",")
}
}
}
tagsParsed := buffer.String()
// Keys are used for multi alerts
var b bytes.Buffer
if raw, ok := d.GetOk("keys"); ok {
list := raw.([]interface{})
b.WriteString(".by(")
length := (len(list) - 1)
for i, v := range list {
b.WriteString(fmt.Sprintf("\"%s\"", v))
if i != length {
b.WriteString(",")
}
}
b.WriteString(")")
}
keys := b.String()
var monitorName string
var query string
check := d.Get("check").(string)
// Examples queries
// "http.can_connect".over("instance:buildeng_http","production").last(2).count_by_status()
// "http.can_connect".over("*").by("host","instance","url").last(2).count_by_status()
checkCount, thresholds := getThresholds(d)
query = fmt.Sprintf("\"%s\".over(%s)%s.last(%s).count_by_status()", check, tagsParsed, keys, checkCount)
log.Print(fmt.Sprintf("[DEBUG] submitting query: %s", query))
monitorName = name
o := datadog.Options{
NotifyNoData: d.Get("notify_no_data").(bool),
NoDataTimeframe: d.Get("no_data_timeframe").(int),
RenotifyInterval: d.Get("renotify_interval").(int),
Thresholds: thresholds,
}
m := datadog.Monitor{
Type: "service check",
Query: query,
Name: monitorName,
Message: message,
Options: o,
}
return &m
}
// resourceDatadogServiceCheckCreate creates a monitor.
func resourceDatadogServiceCheckCreate(d *schema.ResourceData, meta interface{}) error {
log.Print("[DEBUG] creating monitor")
m := buildServiceCheckStruct(d)
if err := monitorCreator(d, meta, m); err != nil {
return err
}
return nil
}
// resourceDatadogServiceCheckUpdate updates a monitor.
func resourceDatadogServiceCheckUpdate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] running update.")
m := buildServiceCheckStruct(d)
if err := monitorUpdater(d, meta, m); err != nil {
return err
}
return nil
}

View File

@ -1,96 +0,0 @@
package datadog
import (
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/zorkian/go-datadog-api"
)
func TestAccDatadogServiceCheck_Basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDatadogServiceCheckDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckDatadogServiceCheckConfigBasic,
Check: resource.ComposeTestCheckFunc(
testAccCheckDatadogServiceCheckExists("datadog_service_check.bar"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "name", "name for service check bar"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "message", "{{#is_alert}}Service check bar is critical"+
"{{/is_alert}}\n{{#is_warning}}Service check bar is at warning "+
"level{{/is_warning}}\n{{#is_recovery}}Service check bar has "+
"recovered{{/is_recovery}}\nNotify: @hipchat-channel\n"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "check", "datadog.agent.up"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "notify_no_data", "false"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "tags.0", "environment:foo"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "tags.1", "host:bar"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "tags.#", "2"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "keys.0", "foo"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "keys.1", "bar"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "keys.#", "2"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "thresholds.ok", "0"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "thresholds.warning", "1"),
resource.TestCheckResourceAttr(
"datadog_service_check.bar", "thresholds.critical", "2"),
),
},
},
})
}
func testAccCheckDatadogServiceCheckDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*datadog.Client)
if err := destroyHelper(s, client); err != nil {
return err
}
return nil
}
func testAccCheckDatadogServiceCheckExists(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 testAccCheckDatadogServiceCheckConfigBasic = `
resource "datadog_service_check" "bar" {
name = "name for service check bar"
message = <<EOF
{{#is_alert}}Service check bar is critical{{/is_alert}}
{{#is_warning}}Service check bar is at warning level{{/is_warning}}
{{#is_recovery}}Service check bar has recovered{{/is_recovery}}
Notify: @hipchat-channel
EOF
tags = ["environment:foo", "host:bar"]
keys = ["foo", "bar"]
check = "datadog.agent.up"
thresholds {
ok = 0
warning = 1
critical = 2
}
notify_no_data = false
}
`

View File

@ -1,138 +0,0 @@
package datadog
import (
"encoding/json"
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"github.com/zorkian/go-datadog-api"
"strconv"
"strings"
)
func thresholdSchema() *schema.Schema {
return &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,
},
},
},
}
}
func getThresholds(d *schema.ResourceData) (string, datadog.ThresholdCount) {
t := datadog.ThresholdCount{}
var threshold string
if r, ok := d.GetOk("thresholds.ok"); ok {
t.Ok = json.Number(r.(string))
}
if r, ok := d.GetOk("thresholds.warning"); ok {
t.Warning = json.Number(r.(string))
}
if r, ok := d.GetOk("thresholds.critical"); ok {
threshold = r.(string)
t.Critical = json.Number(r.(string))
}
return threshold, t
}
func resourceDatadogGenericDelete(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
}
func resourceDatadogGenericExists(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)
// Workaround to handle upgrades from < 0.0.4
if strings.Contains(d.Id(), "__") {
return false, fmt.Errorf("Monitor ID contains __, which is pre v0.0.4 old behaviour.\n You have the following options:\n" +
" * Run https://github.com/ojongerius/terraform-provider-datadog/blob/master/scripts/migration_helper.py to generate a new statefile and clean up monitors\n" +
" * Mannualy fix this by deleting all your metric_check resources and recreate them, " +
"or manually remove half of the resources and hack the state file.\n")
}
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 resourceDatadogGenericRead(d *schema.ResourceData, meta interface{}) error {
// TODO: Add support for read function.
/* Read - This is called to resync the local state with the remote state.
Terraform guarantees that an existing ID will be set. This ID should be
used to look up the resource. Any remote data should be updated into the
local data. No changes to the remote resource are to be made.
*/
return nil
}
func monitorCreator(d *schema.ResourceData, meta interface{}, m *datadog.Monitor) error {
client := meta.(*datadog.Client)
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 monitorUpdater(d *schema.ResourceData, meta interface{}, m *datadog.Monitor) error {
client := meta.(*datadog.Client)
i, err := strconv.Atoi(d.Id())
if err != nil {
return err
}
m.Id = i
if err = client.UpdateMonitor(m); err != nil {
return fmt.Errorf("error updating montor: %s", err.Error())
}
return nil
}

View File

@ -1,34 +0,0 @@
package datadog
import (
"fmt"
"strconv"
"strings"
"github.com/hashicorp/terraform/terraform"
"github.com/zorkian/go-datadog-api"
)
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
}