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/provider.go b/builtin/providers/datadog/provider.go new file mode 100644 index 000000000..d66729d7f --- /dev/null +++ b/builtin/providers/datadog/provider.go @@ -0,0 +1,33 @@ +package datadog + +import ( + "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_metric": datadogMonitorResource(), + }, + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(rd *schema.ResourceData) (interface{}, error) { + apiKey := rd.Get("api_key").(string) + appKey := rd.Get("app_key").(string) + return map[string]string{"api_key": apiKey, "app_key": appKey}, nil +} diff --git a/builtin/providers/datadog/resource_datadog_monitor.go b/builtin/providers/datadog/resource_datadog_monitor.go new file mode 100644 index 000000000..85f4274ff --- /dev/null +++ b/builtin/providers/datadog/resource_datadog_monitor.go @@ -0,0 +1,235 @@ +package datadog + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "strconv" + "strings" + + "github.com/hashicorp/terraform/helper/schema" +) + +const ( + monitorEndpoint = "https://app.datadoghq.com/api/v1/monitor" +) + +func datadogMonitorResource() *schema.Resource { + return &schema.Resource{ + Create: resourceMonitorCreate, + Read: resourceMonitorRead, + Update: resourceMonitorUpdate, + Delete: resourceMonitorDelete, + Exists: resourceMonitorExists, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + // Metric and Monitor settings + "metric": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "metric_tags": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "*", + }, + "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, + }, + "operator": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "message": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + // Alert Settings + "warning": &schema.Schema{ + Type: schema.TypeMap, + Required: true, + }, + "critical": &schema.Schema{ + Type: schema.TypeMap, + 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, + }, + }, + } +} + +func getIDFromResponse(h *http.Response) (string, error) { + body, err := ioutil.ReadAll(h.Body) + if err != nil { + return "", err + } + h.Body.Close() + log.Println(h) + log.Println(string(body)) + v := map[string]interface{}{} + err = json.Unmarshal(body, &v) + if err != nil { + return "", err + } + if id, ok := v["id"]; ok { + return strconv.Itoa(int(id.(float64))), nil + } + return "", fmt.Errorf("error getting ID from response %s", h.Status) +} + +func marshalMetric(d *schema.ResourceData, typeStr string) ([]byte, error) { + 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) + tags := d.Get("metric_tags").(string) + operator := d.Get("operator").(string) + query := fmt.Sprintf("%s(%s):%s:%s{%s} %s %s", timeAggr, timeWindow, spaceAggr, metric, tags, operator, d.Get(fmt.Sprintf("%s.threshold", typeStr))) + + log.Println(query) + m := map[string]interface{}{ + "type": "metric alert", + "query": query, + "name": fmt.Sprintf("[%s] %s", typeStr, name), + "message": fmt.Sprintf("%s %s", message, d.Get(fmt.Sprintf("%s.notify", typeStr))), + "options": map[string]interface{}{ + "notify_no_data": d.Get("notify_no_data").(bool), + "no_data_timeframe": d.Get("no_data_timeframe").(int), + }, + } + return json.Marshal(m) +} + +func authSuffix(meta interface{}) string { + m := meta.(map[string]string) + return fmt.Sprintf("?api_key=%s&application_key=%s", m["api_key"], m["app_key"]) +} + +func resourceMonitorCreate(d *schema.ResourceData, meta interface{}) error { + warningBody, err := marshalMetric(d, "warning") + if err != nil { + return err + } + criticalBody, err := marshalMetric(d, "critical") + if err != nil { + return err + } + + resW, err := http.Post(fmt.Sprintf("%s%s", monitorEndpoint, authSuffix(meta)), "application/json", bytes.NewReader(warningBody)) + if err != nil { + return fmt.Errorf("error creating warning: %s", err.Error()) + } + + resC, err := http.Post(fmt.Sprintf("%s%s", monitorEndpoint, authSuffix(meta)), "application/json", bytes.NewReader(criticalBody)) + if err != nil { + return fmt.Errorf("error creating critical: %s", err.Error()) + } + + warningMonitorID, err := getIDFromResponse(resW) + if err != nil { + return err + } + criticalMonitorID, err := getIDFromResponse(resC) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("%s__%s", warningMonitorID, criticalMonitorID)) + + return nil +} + +func resourceMonitorDelete(d *schema.ResourceData, meta interface{}) (e error) { + for _, v := range strings.Split(d.Id(), "__") { + client := http.Client{} + req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/%s%s", monitorEndpoint, v, authSuffix(meta)), nil) + _, err := client.Do(req) + e = err + } + return +} + +func resourceMonitorExists(d *schema.ResourceData, meta interface{}) (b bool, e error) { + b = true + for _, v := range strings.Split(d.Id(), "__") { + res, err := http.Get(fmt.Sprintf("%s/%s%s", monitorEndpoint, v, authSuffix(meta))) + if err != nil { + e = err + continue + } + if res.StatusCode > 400 { + b = false + continue + } + b = b && true + } + if !b { + e = resourceMonitorDelete(d, meta) + } + return +} + +func resourceMonitorRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceMonitorUpdate(d *schema.ResourceData, meta interface{}) error { + split := strings.Split(d.Id(), "__") + warningID, criticalID := split[0], split[1] + + warningBody, _ := marshalMetric(d, "warning") + criticalBody, _ := marshalMetric(d, "critical") + + client := http.Client{} + + reqW, _ := http.NewRequest("PUT", fmt.Sprintf("%s/%s%s", monitorEndpoint, warningID, authSuffix(meta)), bytes.NewReader(warningBody)) + resW, err := client.Do(reqW) + if err != nil { + return fmt.Errorf("error updating warning: %s", err.Error()) + } + resW.Body.Close() + if resW.StatusCode > 400 { + return fmt.Errorf("error updating warning monitor: %s", resW.Status) + } + + reqC, _ := http.NewRequest("PUT", fmt.Sprintf("%s/%s%s", monitorEndpoint, criticalID, authSuffix(meta)), bytes.NewReader(criticalBody)) + resC, err := client.Do(reqC) + if err != nil { + return fmt.Errorf("error updating critical: %s", err.Error()) + } + resW.Body.Close() + if resW.StatusCode > 400 { + return fmt.Errorf("error updating critical monitor: %s", resC.Status) + } + return nil +}