diff --git a/builtin/providers/librato/common.go b/builtin/providers/librato/common.go new file mode 100644 index 000000000..79056fab7 --- /dev/null +++ b/builtin/providers/librato/common.go @@ -0,0 +1,40 @@ +package librato + +import ( + "encoding/json" + "fmt" +) + +// Encodes a hash into a JSON string +func attributesFlatten(attrs map[string]string) (string, error) { + byteArray, err := json.Marshal(attrs) + if err != nil { + return "", fmt.Errorf("Error encoding to JSON: %s", err) + } + + return string(byteArray), nil +} + +// Takes JSON in a string & decodes into a hash +func attributesExpand(raw string) (map[string]string, error) { + attrs := make(map[string]string) + err := json.Unmarshal([]byte(raw), &attrs) + if err != nil { + return nil, fmt.Errorf("Error decoding JSON: %s", err) + } + + return attrs, err +} + +func normalizeJSON(jsonString interface{}) string { + if jsonString == nil || jsonString == "" { + return "" + } + var j interface{} + err := json.Unmarshal([]byte(jsonString.(string)), &j) + if err != nil { + return fmt.Sprintf("Error parsing JSON: %s", err) + } + b, _ := json.Marshal(j) + return string(b[:]) +} diff --git a/builtin/providers/librato/common_helpers_test.go b/builtin/providers/librato/common_helpers_test.go new file mode 100644 index 000000000..c2744589f --- /dev/null +++ b/builtin/providers/librato/common_helpers_test.go @@ -0,0 +1,12 @@ +package librato + +import ( + "testing" + "time" +) + +func sleep(t *testing.T, amount time.Duration) func() { + return func() { + time.Sleep(amount * time.Second) + } +} diff --git a/builtin/providers/librato/resource_librato_metric.go b/builtin/providers/librato/resource_librato_metric.go index f1d7aa7f8..454105431 100644 --- a/builtin/providers/librato/resource_librato_metric.go +++ b/builtin/providers/librato/resource_librato_metric.go @@ -102,6 +102,8 @@ func resourceLibratoMetric() *schema.Resource { } func resourceLibratoMetricCreate(d *schema.ResourceData, meta interface{}) error { + log.Println("[INFO] Creating Librato metric") + client := meta.(*librato.Client) metric := new(librato.Metric) @@ -120,11 +122,15 @@ func resourceLibratoMetricCreate(d *schema.ResourceData, meta interface{}) error if a, ok := d.GetOk("period"); ok { metric.Period = librato.Uint(a.(uint)) } + if a, ok := d.GetOk("composite"); ok { + metric.Composite = librato.String(a.(string)) + } if a, ok := d.GetOk("attributes"); ok { + attributeData := a.([]interface{}) if len(attributeData) > 1 { - return fmt.Errorf("Only one set of attributes per alert is supported") + return fmt.Errorf("Only one set of attributes per metric is supported") } if len(attributeData) == 1 && attributeData[0] == nil { @@ -168,7 +174,7 @@ func resourceLibratoMetricCreate(d *schema.ResourceData, meta interface{}) error _, err := client.Metrics.Edit(metric) if err != nil { log.Printf("[INFO] ERROR creating Metric: %s", err) - return fmt.Errorf("Error creating Librato service: %s", err) + return fmt.Errorf("Error creating Librato metric: %s", err) } resource.Retry(1*time.Minute, func() *resource.RetryError { @@ -182,72 +188,12 @@ func resourceLibratoMetricCreate(d *schema.ResourceData, meta interface{}) error return nil }) - return resourceLibratoMetricReadResult(d, metric) -} - -func resourceLibratoMetricReadResult(d *schema.ResourceData, metric *librato.Metric) error { - d.SetId(*metric.Name) - d.Set("id", *metric.Name) - d.Set("name", *metric.Name) - d.Set("type", *metric.Type) - - if metric.Description != nil { - d.Set("description", *metric.Description) - } - - if metric.DisplayName != nil { - d.Set("display_name", *metric.DisplayName) - } - - if metric.Period != nil { - d.Set("period", *metric.Period) - } - - if metric.Composite != nil { - d.Set("composite", *metric.Composite) - } - - attributes := resourceLibratoMetricAttributesGather(d, metric.Attributes) - d.Set("attributes", attributes) - - return nil -} - -// Flattens an attributes hash into something that flatmap.Flatten() can handle -func resourceLibratoMetricAttributesGather(d *schema.ResourceData, attributes *librato.MetricAttributes) []map[string]interface{} { - result := make([]map[string]interface{}, 0, 1) - - if attributes != nil { - retAttributes := make(map[string]interface{}) - if attributes.Color != nil { - retAttributes["color"] = *attributes.Color - } - if attributes.DisplayMax != nil { - retAttributes["display_max"] = attributes.DisplayMax - } - if attributes.DisplayMin != nil { - retAttributes["display_min"] = attributes.DisplayMin - } - if attributes.DisplayUnitsLong != "" { - retAttributes["display_units_long"] = attributes.DisplayUnitsLong - } - if attributes.DisplayUnitsShort != "" { - retAttributes["display_units_short"] = attributes.DisplayUnitsShort - } - if attributes.CreatedByUA != "" { - retAttributes["created_by_ua"] = attributes.CreatedByUA - } - retAttributes["display_stacked"] = attributes.DisplayStacked || false - retAttributes["gap_detection"] = attributes.GapDetection || false - retAttributes["aggregate"] = attributes.Aggregate || false - - result = append(result, retAttributes) - } - - return result + return metricReadResult(d, metric) } func resourceLibratoMetricRead(d *schema.ResourceData, meta interface{}) error { + log.Println("[INFO] Reading Librato metric") + client := meta.(*librato.Client) id := d.Id() @@ -264,10 +210,12 @@ func resourceLibratoMetricRead(d *schema.ResourceData, meta interface{}) error { log.Printf("[INFO] Read Librato Metric: %s", structToString(metric)) - return resourceLibratoMetricReadResult(d, metric) + return metricReadResult(d, metric) } func resourceLibratoMetricUpdate(d *schema.ResourceData, meta interface{}) error { + log.Println("[INFO] Updating Librato metric") + client := meta.(*librato.Client) metricID := d.Id() @@ -309,7 +257,7 @@ func resourceLibratoMetricUpdate(d *schema.ResourceData, meta interface{}) error if d.HasChange("attributes") { attributeData := d.Get("attributes").([]interface{}) if len(attributeData) > 1 { - return fmt.Errorf("Only one set of attributes per alert is supported") + return fmt.Errorf("Only one set of attributes per metric is supported") } if len(attributeData) == 1 && attributeData[0] == nil { @@ -351,7 +299,9 @@ func resourceLibratoMetricUpdate(d *schema.ResourceData, meta interface{}) error fullMetric.Attributes = attributes } - log.Printf("[INFO] Updating Librato metric: %v", metric) + log.Printf("[INFO] Updating Librato metric: %v", structToString(metric)) + log.Printf("[INFO] Librato fullMetric: %v", structToString(fullMetric)) + _, err = client.Metrics.Edit(metric) if err != nil { return fmt.Errorf("Error updating Librato metric: %s", err) @@ -367,19 +317,20 @@ func resourceLibratoMetricUpdate(d *schema.ResourceData, meta interface{}) error MinTimeout: 2 * time.Second, ContinuousTargetOccurence: 5, Refresh: func() (interface{}, string, error) { - log.Printf("[DEBUG] Checking if Librato Metric %s was updated yet", metricID) + log.Printf("[INFO] Checking if Librato Metric %s was updated yet", metricID) changedMetric, _, getErr := client.Metrics.Get(metricID) if getErr != nil { return changedMetric, "", getErr } isEqual := reflect.DeepEqual(*fullMetric, *changedMetric) - log.Printf("[DEBUG] Updated Librato Metric %s match: %t", metricID, isEqual) + log.Printf("[INFO] Updated Librato Metric %s match: %t", metricID, isEqual) return changedMetric, fmt.Sprintf("%t", isEqual), nil }, } _, err = wait.WaitForState() if err != nil { + log.Printf("[INFO] ERROR - Failed updating Librato Metric %s: %s", metricID, err) return fmt.Errorf("Failed updating Librato Metric %s: %s", metricID, err) } @@ -387,6 +338,8 @@ func resourceLibratoMetricUpdate(d *schema.ResourceData, meta interface{}) error } func resourceLibratoMetricDelete(d *schema.ResourceData, meta interface{}) error { + log.Println("[INFO] Deleting Librato metric") + client := meta.(*librato.Client) id := d.Id() @@ -397,22 +350,92 @@ func resourceLibratoMetricDelete(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error deleting Metric: %s", err) } + log.Printf("[INFO] Verifying Metric %s deleted", id) resource.Retry(1*time.Minute, func() *resource.RetryError { + + log.Printf("[INFO] GETing Metric %s", id) _, _, err := client.Metrics.Get(id) if err != nil { if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 { + log.Printf("[INFO] GET returned a 404 for Metric %s\n", id) return nil } log.Printf("[INFO] non-retryable error attempting to Get metric: %s", err) return resource.NonRetryableError(err) } + + log.Printf("[INFO] retryable error attempting to Get metric: %s", id) return resource.RetryableError(fmt.Errorf("metric still exists")) }) + log.Println("[INFO] I think Metric is deleted") + d.SetId("") return nil } +func metricReadResult(d *schema.ResourceData, metric *librato.Metric) error { + d.SetId(*metric.Name) + d.Set("id", *metric.Name) + d.Set("name", *metric.Name) + d.Set("type", *metric.Type) + + if metric.Description != nil { + d.Set("description", *metric.Description) + } + + if metric.DisplayName != nil { + d.Set("display_name", *metric.DisplayName) + } + + if metric.Period != nil { + d.Set("period", *metric.Period) + } + + if metric.Composite != nil { + d.Set("composite", *metric.Composite) + } + + attributes := metricAttributesGather(d, metric.Attributes) + d.Set("attributes", attributes) + + return nil +} + +// Flattens an attributes hash into something that flatmap.Flatten() can handle +func metricAttributesGather(d *schema.ResourceData, attributes *librato.MetricAttributes) []map[string]interface{} { + result := make([]map[string]interface{}, 0, 1) + + if attributes != nil { + retAttributes := make(map[string]interface{}) + if attributes.Color != nil { + retAttributes["color"] = *attributes.Color + } + if attributes.DisplayMax != nil { + retAttributes["display_max"] = attributes.DisplayMax + } + if attributes.DisplayMin != nil { + retAttributes["display_min"] = attributes.DisplayMin + } + if attributes.DisplayUnitsLong != "" { + retAttributes["display_units_long"] = attributes.DisplayUnitsLong + } + if attributes.DisplayUnitsShort != "" { + retAttributes["display_units_short"] = attributes.DisplayUnitsShort + } + if attributes.CreatedByUA != "" { + retAttributes["created_by_ua"] = attributes.CreatedByUA + } + retAttributes["display_stacked"] = attributes.DisplayStacked || false + retAttributes["gap_detection"] = attributes.GapDetection || false + retAttributes["aggregate"] = attributes.Aggregate || false + + result = append(result, retAttributes) + } + + return result +} + func structToString(i interface{}) string { s, _ := json.Marshal(i) return string(s) diff --git a/builtin/providers/librato/resource_librato_metric_test.go b/builtin/providers/librato/resource_librato_metric_test.go index a5227153f..420259951 100644 --- a/builtin/providers/librato/resource_librato_metric_test.go +++ b/builtin/providers/librato/resource_librato_metric_test.go @@ -11,10 +11,10 @@ import ( "github.com/henrikhodne/go-librato/librato" ) -func TestAccLibratoMetric_Basic(t *testing.T) { +func TestAccLibratoCounterMetric(t *testing.T) { var metric librato.Metric name := fmt.Sprintf("tftest-metric-%s", acctest.RandString(10)) - typ := "counter" + counter := "counter" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -22,7 +22,88 @@ func TestAccLibratoMetric_Basic(t *testing.T) { CheckDestroy: testAccCheckLibratoMetricDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckLibratoMetricConfig(name, typ), + Config: counterMetricConfig(name, counter, fmt.Sprintf("A test %s metric", counter)), + Check: resource.ComposeTestCheckFunc( + testAccCheckLibratoMetricExists("librato_metric.foobar", &metric), + testAccCheckLibratoMetricName(&metric, name), + testAccCheckLibratoMetricType(&metric, []string{"gauge", "counter", "composite"}), + resource.TestCheckResourceAttr( + "librato_metric.foobar", "name", name), + ), + }, + { + PreConfig: sleep(t, 15), + Config: counterMetricConfig(name, counter, fmt.Sprintf("An updated test %s metric", counter)), + Check: resource.ComposeTestCheckFunc( + testAccCheckLibratoMetricExists("librato_metric.foobar", &metric), + testAccCheckLibratoMetricName(&metric, name), + testAccCheckLibratoMetricType(&metric, []string{"gauge", "counter", "composite"}), + resource.TestCheckResourceAttr( + "librato_metric.foobar", "name", name), + ), + }, + }, + }) +} + +func TestAccLibratoGaugeMetric(t *testing.T) { + var metric librato.Metric + name := fmt.Sprintf("tftest-metric-%s", acctest.RandString(10)) + gauge := "gauge" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibratoMetricDestroy, + Steps: []resource.TestStep{ + { + Config: gaugeMetricConfig(name, gauge, fmt.Sprintf("A test %s metric", gauge)), + Check: resource.ComposeTestCheckFunc( + testAccCheckLibratoMetricExists("librato_metric.foobar", &metric), + testAccCheckLibratoMetricName(&metric, name), + testAccCheckLibratoMetricType(&metric, []string{"gauge", "counter", "composite"}), + resource.TestCheckResourceAttr( + "librato_metric.foobar", "name", name), + ), + }, + { + PreConfig: sleep(t, 15), + Config: gaugeMetricConfig(name, gauge, fmt.Sprintf("An updated test %s metric", gauge)), + Check: resource.ComposeTestCheckFunc( + testAccCheckLibratoMetricExists("librato_metric.foobar", &metric), + testAccCheckLibratoMetricName(&metric, name), + testAccCheckLibratoMetricType(&metric, []string{"gauge", "counter", "composite"}), + resource.TestCheckResourceAttr( + "librato_metric.foobar", "name", name), + ), + }, + }, + }) +} + +func TestAccLibratoCompositeMetric(t *testing.T) { + var metric librato.Metric + name := fmt.Sprintf("tftest-metric-%s", acctest.RandString(10)) + composite := "composite" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibratoMetricDestroy, + Steps: []resource.TestStep{ + { + Config: compositeMetricConfig(name, composite, fmt.Sprintf("A test %s metric", composite)), + Check: resource.ComposeTestCheckFunc( + testAccCheckLibratoMetricExists("librato_metric.foobar", &metric), + testAccCheckLibratoMetricName(&metric, name), + testAccCheckLibratoMetricType(&metric, []string{"gauge", "counter", "composite"}), + resource.TestCheckResourceAttr( + "librato_metric.foobar", "name", name), + ), + }, + { + PreConfig: sleep(t, 15), + Config: compositeMetricConfig(name, composite, fmt.Sprintf("An updated test %s metric", composite)), Check: resource.ComposeTestCheckFunc( testAccCheckLibratoMetricExists("librato_metric.foobar", &metric), testAccCheckLibratoMetricName(&metric, name), @@ -43,6 +124,7 @@ func testAccCheckLibratoMetricDestroy(s *terraform.State) error { continue } + fmt.Printf("*** testAccCheckLibratoMetricDestroy ID: %s\n", rs.Primary.ID) _, _, err := client.Metrics.Get(rs.Primary.ID) if err == nil { return fmt.Errorf("Metric still exists") @@ -52,20 +134,6 @@ func testAccCheckLibratoMetricDestroy(s *terraform.State) error { return nil } -func testAccCheckLibratoMetricConfig(name, typ string) string { - return strings.TrimSpace(fmt.Sprintf(` - resource "librato_metric" "foobar" { - name = "%s" - type = "%s" - description = "A test composite metric" - composite = "s(\"librato.cpu.percent.user\", {\"environment\" : \"prod\", \"service\": \"api\"})" - attributes { - display_stacked = true, - created_by_ua = "go-librato/0.1" - } - }`, name, typ)) -} - func testAccCheckLibratoMetricName(metric *librato.Metric, name string) resource.TestCheckFunc { return func(s *terraform.State) error { if metric.Name == nil || *metric.Name != name { @@ -120,3 +188,43 @@ func testAccCheckLibratoMetricExists(n string, metric *librato.Metric) resource. return nil } } + +func counterMetricConfig(name, typ, desc string) string { + return strings.TrimSpace(fmt.Sprintf(` + resource "librato_metric" "foobar" { + name = "%s" + type = "%s" + description = "%s" + attributes { + display_stacked = true, + created_by_ua = "go-librato/0.1" + } + }`, name, typ, desc)) +} + +func gaugeMetricConfig(name, typ, desc string) string { + return strings.TrimSpace(fmt.Sprintf(` + resource "librato_metric" "foobar" { + name = "%s" + type = "%s" + description = "%s" + attributes { + display_stacked = true, + created_by_ua = "go-librato/0.1" + } + }`, name, typ, desc)) +} + +func compositeMetricConfig(name, typ, desc string) string { + return strings.TrimSpace(fmt.Sprintf(` + resource "librato_metric" "foobar" { + name = "%s" + type = "%s" + description = "%s" + composite = "s(\"librato.cpu.percent.user\", {\"environment\" : \"prod\", \"service\": \"api\"})" + attributes { + display_stacked = true, + created_by_ua = "go-librato/0.1" + } + }`, name, typ, desc)) +} diff --git a/builtin/providers/librato/resource_librato_service.go b/builtin/providers/librato/resource_librato_service.go index e289fee0d..855fc4041 100644 --- a/builtin/providers/librato/resource_librato_service.go +++ b/builtin/providers/librato/resource_librato_service.go @@ -1,7 +1,6 @@ package librato import ( - "encoding/json" "fmt" "log" "reflect" @@ -37,49 +36,12 @@ func resourceLibratoService() *schema.Resource { "settings": &schema.Schema{ Type: schema.TypeString, Required: true, - StateFunc: normalizeJson, + StateFunc: normalizeJSON, }, }, } } -// Takes JSON in a string. Decodes JSON into -// settings hash -func resourceLibratoServicesExpandSettings(rawSettings string) (map[string]string, error) { - var settings map[string]string - - settings = make(map[string]string) - err := json.Unmarshal([]byte(rawSettings), &settings) - if err != nil { - return nil, fmt.Errorf("Error decoding JSON: %s", err) - } - - return settings, err -} - -// Encodes a settings hash into a JSON string -func resourceLibratoServicesFlatten(settings map[string]string) (string, error) { - byteArray, err := json.Marshal(settings) - if err != nil { - return "", fmt.Errorf("Error encoding to JSON: %s", err) - } - - return string(byteArray), nil -} - -func normalizeJson(jsonString interface{}) string { - if jsonString == nil || jsonString == "" { - return "" - } - var j interface{} - err := json.Unmarshal([]byte(jsonString.(string)), &j) - if err != nil { - return fmt.Sprintf("Error parsing JSON: %s", err) - } - b, _ := json.Marshal(j) - return string(b[:]) -} - func resourceLibratoServiceCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*librato.Client) @@ -91,7 +53,7 @@ func resourceLibratoServiceCreate(d *schema.ResourceData, meta interface{}) erro service.Title = librato.String(v.(string)) } if v, ok := d.GetOk("settings"); ok { - res, err := resourceLibratoServicesExpandSettings(normalizeJson(v.(string))) + res, err := attributesExpand(normalizeJSON(v.(string))) if err != nil { return fmt.Errorf("Error expanding Librato service settings: %s", err) } @@ -144,7 +106,7 @@ func resourceLibratoServiceReadResult(d *schema.ResourceData, service *librato.S d.Set("id", *service.ID) d.Set("type", *service.Type) d.Set("title", *service.Title) - settings, _ := resourceLibratoServicesFlatten(service.Settings) + settings, _ := attributesFlatten(service.Settings) d.Set("settings", settings) return nil @@ -174,9 +136,9 @@ func resourceLibratoServiceUpdate(d *schema.ResourceData, meta interface{}) erro fullService.Title = service.Title } if d.HasChange("settings") { - res, err := resourceLibratoServicesExpandSettings(normalizeJson(d.Get("settings").(string))) - if err != nil { - return fmt.Errorf("Error expanding Librato service settings: %s", err) + res, getErr := attributesExpand(normalizeJSON(d.Get("settings").(string))) + if getErr != nil { + return fmt.Errorf("Error expanding Librato service settings: %s", getErr) } service.Settings = res fullService.Settings = res @@ -198,9 +160,9 @@ func resourceLibratoServiceUpdate(d *schema.ResourceData, meta interface{}) erro ContinuousTargetOccurence: 5, Refresh: func() (interface{}, string, error) { log.Printf("[DEBUG] Checking if Librato Service %d was updated yet", serviceID) - changedService, _, err := client.Services.Get(uint(serviceID)) - if err != nil { - return changedService, "", err + changedService, _, scErr := client.Services.Get(uint(serviceID)) + if scErr != nil { + return changedService, "", scErr } isEqual := reflect.DeepEqual(*fullService, *changedService) log.Printf("[DEBUG] Updated Librato Service %d match: %t", serviceID, isEqual)