├── README.md ├── monitor ├── read.go ├── delete.go ├── exists.go ├── update.go ├── resource.go └── create.go ├── main.go ├── example ├── monitor.tf ├── terraform.tfstate └── terraform.tfstate.backup └── datadog.go /README.md: -------------------------------------------------------------------------------- 1 | # terraform-datadog 2 | -------------------------------------------------------------------------------- /monitor/read.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import "github.com/hashicorp/terraform/helper/schema" 4 | 5 | func Read(d *schema.ResourceData, meta interface{}) error { 6 | return nil 7 | } 8 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/hashicorp/terraform/plugin" 4 | 5 | func main() { 6 | plugin.Serve(&plugin.ServeOpts{ 7 | ProviderFunc: Provider, 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /example/monitor.tf: -------------------------------------------------------------------------------- 1 | resource "datadog_monitor_metric" "test" { 2 | name = "App metric" 3 | message = "This is so good" 4 | 5 | metric = "app.some.metric" 6 | time_aggr = "avg" 7 | time_window = "last_5m" 8 | space_aggr = "avg" 9 | operator = ">" 10 | 11 | warning { 12 | threshold = 80 13 | notify = "@slack-team-infra" 14 | } 15 | 16 | critical { 17 | threshold = 100 18 | notify = "@slack-team-infra @pagerduty" 19 | } 20 | } -------------------------------------------------------------------------------- /monitor/delete.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/hashicorp/terraform/helper/schema" 9 | ) 10 | 11 | func Delete(d *schema.ResourceData, meta interface{}) (e error) { 12 | for _, v := range strings.Split(d.Id(), "__") { 13 | client := http.Client{} 14 | req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/%s%s", MONITOR_ENDPOINT, v, AuthSuffix(meta)), nil) 15 | _, err := client.Do(req) 16 | e = err 17 | } 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /monitor/exists.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/hashicorp/terraform/helper/schema" 9 | ) 10 | 11 | func Exists(d *schema.ResourceData, meta interface{}) (b bool, e error) { 12 | b = true 13 | for _, v := range strings.Split(d.Id(), "__") { 14 | res, err := http.Get(fmt.Sprintf("%s/%s%s", MONITOR_ENDPOINT, v, AuthSuffix(meta))) 15 | if err != nil { 16 | e = err 17 | continue 18 | } 19 | if res.StatusCode > 400 { 20 | b = false 21 | continue 22 | } 23 | b = b && true 24 | } 25 | if !b { 26 | e = Delete(d, meta) 27 | } 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /datadog.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hashicorp/terraform/helper/schema" 5 | "github.com/hashicorp/terraform/terraform" 6 | "github.com/segmentio/terraform-datadog/monitor" 7 | ) 8 | 9 | func Provider() terraform.ResourceProvider { 10 | return &schema.Provider{ 11 | Schema: map[string]*schema.Schema{ 12 | "api_key": &schema.Schema{ 13 | Type: schema.TypeString, 14 | Required: true, 15 | DefaultFunc: schema.EnvDefaultFunc("DATADOG_API_KEY", nil), 16 | }, 17 | "app_key": &schema.Schema{ 18 | Type: schema.TypeString, 19 | Required: true, 20 | DefaultFunc: schema.EnvDefaultFunc("DATADOG_APP_KEY", nil), 21 | }, 22 | }, 23 | ResourcesMap: map[string]*schema.Resource{ 24 | "datadog_monitor_metric": monitor.Resource(), 25 | }, 26 | ConfigureFunc: providerConfigure, 27 | } 28 | } 29 | 30 | func providerConfigure(rd *schema.ResourceData) (interface{}, error) { 31 | apiKey := rd.Get("api_key").(string) 32 | appKey := rd.Get("app_key").(string) 33 | return map[string]string{"api_key": apiKey, "app_key": appKey}, nil 34 | } 35 | -------------------------------------------------------------------------------- /monitor/update.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/hashicorp/terraform/helper/schema" 10 | ) 11 | 12 | func Update(d *schema.ResourceData, meta interface{}) error { 13 | split := strings.Split(d.Id(), "__") 14 | warningID, criticalID := split[0], split[1] 15 | 16 | warningBody, _ := MarshalMetric(d, "warning") 17 | criticalBody, _ := MarshalMetric(d, "critical") 18 | 19 | client := http.Client{} 20 | 21 | reqW, _ := http.NewRequest("PUT", fmt.Sprintf("%s/%s%s", MONITOR_ENDPOINT, warningID, AuthSuffix(meta)), bytes.NewReader(warningBody)) 22 | resW, err := client.Do(reqW) 23 | if err != nil { 24 | return fmt.Errorf("error updating warning: %s", err.Error()) 25 | } 26 | resW.Body.Close() 27 | if resW.StatusCode > 400 { 28 | return fmt.Errorf("error updating warning monitor: %s", resW.Status) 29 | } 30 | 31 | reqC, _ := http.NewRequest("PUT", fmt.Sprintf("%s/%s%s", MONITOR_ENDPOINT, criticalID, AuthSuffix(meta)), bytes.NewReader(criticalBody)) 32 | resC, err := client.Do(reqC) 33 | if err != nil { 34 | return fmt.Errorf("error updating critical: %s", err.Error()) 35 | } 36 | resW.Body.Close() 37 | if resW.StatusCode > 400 { 38 | return fmt.Errorf("error updating critical monitor: %s", resC.Status) 39 | } 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /example/terraform.tfstate: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "serial": 14, 4 | "modules": [ 5 | { 6 | "path": [ 7 | "root" 8 | ], 9 | "outputs": {}, 10 | "resources": { 11 | "datadog_monitor_metric.test": { 12 | "type": "datadog_monitor_metric", 13 | "primary": { 14 | "id": "194983__194984", 15 | "attributes": { 16 | "critical.#": "2", 17 | "critical.notify": "@slack-team-infra @pagerduty", 18 | "critical.threshold": "100", 19 | "id": "194983__194984", 20 | "message": "This is so good", 21 | "metric": "app.some.metric", 22 | "metric_tags": "*", 23 | "name": "App metric", 24 | "notify_no_data": "true", 25 | "operator": "\u003e", 26 | "space_aggr": "avg", 27 | "time_aggr": "avg", 28 | "time_window": "last_5m", 29 | "warning.#": "2", 30 | "warning.notify": "@slack-team-infra", 31 | "warning.threshold": "80" 32 | } 33 | } 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /example/terraform.tfstate.backup: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "serial": 13, 4 | "modules": [ 5 | { 6 | "path": [ 7 | "root" 8 | ], 9 | "outputs": {}, 10 | "resources": { 11 | "datadog_monitor_metric.test": { 12 | "type": "datadog_monitor_metric", 13 | "primary": { 14 | "id": "194980__194981", 15 | "attributes": { 16 | "critical.#": "2", 17 | "critical.notify": "@slack-team-infra @pagerduty", 18 | "critical.threshold": "100", 19 | "id": "194980__194981", 20 | "message": "This is so good", 21 | "metric": "app.some.metric", 22 | "metric_tags": "*", 23 | "name": "App metric", 24 | "notify_no_data": "true", 25 | "operator": "\u003e", 26 | "space_aggr": "avg", 27 | "time_aggr": "avg", 28 | "time_window": "last_5m", 29 | "warning.#": "2", 30 | "warning.notify": "@slack-team-infra", 31 | "warning.threshold": "80" 32 | } 33 | } 34 | } 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /monitor/resource.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import "github.com/hashicorp/terraform/helper/schema" 4 | 5 | const ( 6 | MONITOR_ENDPOINT = "https://app.datadoghq.com/api/v1/monitor" 7 | ) 8 | 9 | var ( 10 | AUTH_SUFFIX = "" 11 | ) 12 | 13 | func Resource() *schema.Resource { 14 | return &schema.Resource{ 15 | Create: Create, 16 | Read: Read, 17 | Update: Update, 18 | Delete: Delete, 19 | Exists: Exists, 20 | 21 | Schema: map[string]*schema.Schema{ 22 | "name": &schema.Schema{ 23 | Type: schema.TypeString, 24 | Required: true, 25 | }, 26 | // Metric and Monitor settings 27 | "metric": &schema.Schema{ 28 | Type: schema.TypeString, 29 | Required: true, 30 | }, 31 | "metric_tags": &schema.Schema{ 32 | Type: schema.TypeString, 33 | Optional: true, 34 | Default: "*", 35 | }, 36 | "time_aggr": &schema.Schema{ 37 | Type: schema.TypeString, 38 | Required: true, 39 | }, 40 | "time_window": &schema.Schema{ 41 | Type: schema.TypeString, 42 | Required: true, 43 | }, 44 | "space_aggr": &schema.Schema{ 45 | Type: schema.TypeString, 46 | Required: true, 47 | }, 48 | "operator": &schema.Schema{ 49 | Type: schema.TypeString, 50 | Required: true, 51 | }, 52 | "message": &schema.Schema{ 53 | Type: schema.TypeString, 54 | Required: true, 55 | }, 56 | "metric_key": &schema.Schema{ 57 | Type: schema.TypeString, 58 | Optional: true, 59 | Default: "", 60 | }, 61 | 62 | // Alert Settings 63 | "warning": &schema.Schema{ 64 | Type: schema.TypeMap, 65 | Required: true, 66 | }, 67 | "critical": &schema.Schema{ 68 | Type: schema.TypeMap, 69 | Required: true, 70 | }, 71 | 72 | // Additional Settings 73 | "notify_no_data": &schema.Schema{ 74 | Type: schema.TypeBool, 75 | Optional: true, 76 | Default: true, 77 | }, 78 | 79 | "no_data_timeframe": &schema.Schema{ 80 | Type: schema.TypeInt, 81 | Optional: true, 82 | }, 83 | }, 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /monitor/create.go: -------------------------------------------------------------------------------- 1 | package monitor 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "strconv" 11 | 12 | "github.com/hashicorp/terraform/helper/schema" 13 | ) 14 | 15 | func GetIDFromResponse(h *http.Response) (string, error) { 16 | body, err := ioutil.ReadAll(h.Body) 17 | if err != nil { 18 | return "", err 19 | } 20 | h.Body.Close() 21 | log.Println(h) 22 | log.Println(string(body)) 23 | v := map[string]interface{}{} 24 | err = json.Unmarshal(body, &v) 25 | if err != nil { 26 | return "", err 27 | } 28 | if id, ok := v["id"]; ok { 29 | return strconv.Itoa(int(id.(float64))), nil 30 | } 31 | return "", fmt.Errorf("error getting ID from response %s", h.Status) 32 | } 33 | 34 | func MarshalMetric(d *schema.ResourceData, typeStr string) ([]byte, error) { 35 | name := d.Get("name").(string) 36 | message := d.Get("message").(string) 37 | timeAggr := d.Get("time_aggr").(string) 38 | timeWindow := d.Get("time_window").(string) 39 | spaceAggr := d.Get("space_aggr").(string) 40 | metric := d.Get("metric").(string) 41 | tags := d.Get("metric_tags").(string) 42 | operator := d.Get("operator").(string) 43 | var key string 44 | if k, ok := d.Get("metric_key").(string); ok && k != "" { 45 | key = fmt.Sprintf(" by {%s}", k) 46 | } 47 | query := fmt.Sprintf("%s(%s):%s:%s{%s}%s %s %s", timeAggr, timeWindow, spaceAggr, metric, tags, key, operator, d.Get(fmt.Sprintf("%s.threshold", typeStr))) 48 | 49 | log.Println(query) 50 | m := map[string]interface{}{ 51 | "type": "metric alert", 52 | "query": query, 53 | "name": fmt.Sprintf("[%s] %s", typeStr, name), 54 | "message": fmt.Sprintf("%s %s", message, d.Get(fmt.Sprintf("%s.notify", typeStr))), 55 | "options": map[string]interface{}{ 56 | "notify_no_data": d.Get("notify_no_data").(bool), 57 | "no_data_timeframe": d.Get("no_data_timeframe").(int), 58 | }, 59 | } 60 | return json.Marshal(m) 61 | } 62 | 63 | func AuthSuffix(meta interface{}) string { 64 | m := meta.(map[string]string) 65 | return fmt.Sprintf("?api_key=%s&application_key=%s", m["api_key"], m["app_key"]) 66 | } 67 | 68 | func Create(d *schema.ResourceData, meta interface{}) error { 69 | warningBody, _ := MarshalMetric(d, "warning") 70 | criticalBody, _ := MarshalMetric(d, "critical") 71 | 72 | resW, err := http.Post(fmt.Sprintf("%s%s", MONITOR_ENDPOINT, AuthSuffix(meta)), "application/json", bytes.NewReader(warningBody)) 73 | if err != nil { 74 | return fmt.Errorf("error creating warning: %s", err.Error()) 75 | } 76 | 77 | resC, err := http.Post(fmt.Sprintf("%s%s", MONITOR_ENDPOINT, AuthSuffix(meta)), "application/json", bytes.NewReader(criticalBody)) 78 | if err != nil { 79 | return fmt.Errorf("error creating critical: %s", err.Error()) 80 | } 81 | 82 | warningMonitorID, err := GetIDFromResponse(resW) 83 | if err != nil { 84 | return err 85 | } 86 | criticalMonitorID, err := GetIDFromResponse(resC) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | d.SetId(fmt.Sprintf("%s__%s", warningMonitorID, criticalMonitorID)) 92 | 93 | return nil 94 | } 95 | --------------------------------------------------------------------------------