diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index fc9201945..1a61d3e19 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -787,6 +787,10 @@ "ImportPath": "github.com/hashicorp/yamux", "Rev": "df949784da9ed028ee76df44652e42d37a09d7e4" }, + { + "ImportPath": "github.com/henrikhodne/go-librato/librato", + "Rev": "613abdebf4922c4d9d46bcb4bcf14ee18c08d7de" + }, { "ImportPath": "github.com/hmrc/vmware-govcd", "Comment": "v0.0.2-37-g5cd82f0", diff --git a/builtin/bins/provider-librato/main.go b/builtin/bins/provider-librato/main.go new file mode 100644 index 000000000..557e973a1 --- /dev/null +++ b/builtin/bins/provider-librato/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/librato" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: librato.Provider, + }) +} diff --git a/builtin/bins/provider-librato/main_test.go b/builtin/bins/provider-librato/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/builtin/bins/provider-librato/main_test.go @@ -0,0 +1 @@ +package main diff --git a/builtin/providers/librato/provider.go b/builtin/providers/librato/provider.go new file mode 100644 index 000000000..0b7894f6f --- /dev/null +++ b/builtin/providers/librato/provider.go @@ -0,0 +1,41 @@ +package librato + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" + "github.com/henrikhodne/go-librato/librato" +) + +// Provider returns a schema.Provider for Librato. +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "email": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("LIBRATO_EMAIL", nil), + Description: "The email address for the Librato account.", + }, + + "token": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("LIBRATO_TOKEN", nil), + Description: "The auth token for the Librato account.", + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "librato_space": resourceLibratoSpace(), + "librato_space_chart": resourceLibratoSpaceChart(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + client := librato.NewClient(d.Get("email").(string), d.Get("token").(string)) + + return client, nil +} diff --git a/builtin/providers/librato/provider_test.go b/builtin/providers/librato/provider_test.go new file mode 100644 index 000000000..f25f17fe2 --- /dev/null +++ b/builtin/providers/librato/provider_test.go @@ -0,0 +1,39 @@ +package librato + +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{ + "librato": 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("LIBRATO_EMAIL"); v == "" { + t.Fatal("LIBRATO_EMAIL must be set for acceptance tests") + } + + if v := os.Getenv("LIBRATO_TOKEN"); v == "" { + t.Fatal("LIBRATO_TOKEN must be set for acceptance tests") + } +} diff --git a/builtin/providers/librato/resource_librato_space.go b/builtin/providers/librato/resource_librato_space.go new file mode 100644 index 000000000..e0c1242a4 --- /dev/null +++ b/builtin/providers/librato/resource_librato_space.go @@ -0,0 +1,134 @@ +package librato + +import ( + "fmt" + "log" + "strconv" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/henrikhodne/go-librato/librato" +) + +func resourceLibratoSpace() *schema.Resource { + return &schema.Resource{ + Create: resourceLibratoSpaceCreate, + Read: resourceLibratoSpaceRead, + Update: resourceLibratoSpaceUpdate, + Delete: resourceLibratoSpaceDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: false, + }, + "id": &schema.Schema{ + Type: schema.TypeInt, + Computed: true, + }, + }, + } +} + +func resourceLibratoSpaceCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*librato.Client) + + name := d.Get("name").(string) + + space, _, err := client.Spaces.Create(&librato.Space{Name: librato.String(name)}) + if err != nil { + return fmt.Errorf("Error creating Librato space %s: %s", name, err) + } + + resource.Retry(1*time.Minute, func() *resource.RetryError { + _, _, err := client.Spaces.Get(*space.ID) + if err != nil { + if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + + return resourceLibratoSpaceReadResult(d, space) +} + +func resourceLibratoSpaceRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*librato.Client) + + id, err := strconv.ParseUint(d.Id(), 10, 0) + if err != nil { + return err + } + + space, _, err := client.Spaces.Get(uint(id)) + if err != nil { + if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading Librato Space %s: %s", d.Id(), err) + } + + return resourceLibratoSpaceReadResult(d, space) +} + +func resourceLibratoSpaceReadResult(d *schema.ResourceData, space *librato.Space) error { + d.SetId(strconv.FormatUint(uint64(*space.ID), 10)) + if err := d.Set("id", *space.ID); err != nil { + return err + } + if err := d.Set("name", *space.Name); err != nil { + return err + } + return nil +} + +func resourceLibratoSpaceUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*librato.Client) + id, err := strconv.ParseUint(d.Id(), 10, 0) + if err != nil { + return err + } + + if d.HasChange("name") { + newName := d.Get("name").(string) + log.Printf("[INFO] Modifying name space attribute for %d: %#v", id, newName) + if _, err = client.Spaces.Edit(uint(id), &librato.Space{Name: &newName}); err != nil { + return err + } + } + + return resourceLibratoSpaceRead(d, meta) +} + +func resourceLibratoSpaceDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*librato.Client) + id, err := strconv.ParseUint(d.Id(), 10, 0) + if err != nil { + return err + } + + log.Printf("[INFO] Deleting Space: %d", id) + _, err = client.Spaces.Delete(uint(id)) + if err != nil { + return fmt.Errorf("Error deleting space: %s", err) + } + + resource.Retry(1*time.Minute, func() *resource.RetryError { + _, _, err := client.Spaces.Get(uint(id)) + if err != nil { + if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 { + return nil + } + return resource.NonRetryableError(err) + } + return resource.RetryableError(fmt.Errorf("space still exists")) + }) + + d.SetId("") + return nil +} diff --git a/builtin/providers/librato/resource_librato_space_chart.go b/builtin/providers/librato/resource_librato_space_chart.go new file mode 100644 index 000000000..dea499974 --- /dev/null +++ b/builtin/providers/librato/resource_librato_space_chart.go @@ -0,0 +1,447 @@ +package librato + +import ( + "bytes" + "fmt" + "log" + "math" + "strconv" + "time" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/henrikhodne/go-librato/librato" +) + +func resourceLibratoSpaceChart() *schema.Resource { + return &schema.Resource{ + Create: resourceLibratoSpaceChartCreate, + Read: resourceLibratoSpaceChartRead, + Update: resourceLibratoSpaceChartUpdate, + Delete: resourceLibratoSpaceChartDelete, + + Schema: map[string]*schema.Schema{ + "space_id": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "min": &schema.Schema{ + Type: schema.TypeFloat, + Default: math.NaN(), + Optional: true, + }, + "max": &schema.Schema{ + Type: schema.TypeFloat, + Default: math.NaN(), + Optional: true, + }, + "label": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "related_space": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "stream": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "metric": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"stream.composite"}, + }, + "source": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"stream.composite"}, + }, + "group_function": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"stream.composite"}, + }, + "composite": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"stream.metric", "stream.source", "stream.group_function"}, + }, + "summary_function": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "color": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "units_short": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "units_long": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "min": &schema.Schema{ + Type: schema.TypeFloat, + Default: math.NaN(), + Optional: true, + }, + "max": &schema.Schema{ + Type: schema.TypeFloat, + Default: math.NaN(), + Optional: true, + }, + "transform_function": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "period": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + Set: resourceLibratoSpaceChartHash, + }, + }, + } +} + +func resourceLibratoSpaceChartHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["metric"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["source"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["composite"].(string))) + + return hashcode.String(buf.String()) +} + +func resourceLibratoSpaceChartCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*librato.Client) + + spaceID := uint(d.Get("space_id").(int)) + + spaceChart := new(librato.SpaceChart) + if v, ok := d.GetOk("name"); ok { + spaceChart.Name = librato.String(v.(string)) + } + if v, ok := d.GetOk("type"); ok { + spaceChart.Type = librato.String(v.(string)) + } + if v, ok := d.GetOk("min"); ok { + if math.IsNaN(v.(float64)) { + spaceChart.Min = nil + } else { + spaceChart.Min = librato.Float(v.(float64)) + } + } + if v, ok := d.GetOk("max"); ok { + if math.IsNaN(v.(float64)) { + spaceChart.Max = nil + } else { + spaceChart.Max = librato.Float(v.(float64)) + } + } + if v, ok := d.GetOk("label"); ok { + spaceChart.Label = librato.String(v.(string)) + } + if v, ok := d.GetOk("related_space"); ok { + spaceChart.RelatedSpace = librato.Uint(uint(v.(int))) + } + if v, ok := d.GetOk("stream"); ok { + vs := v.(*schema.Set) + streams := make([]librato.SpaceChartStream, vs.Len()) + for i, streamDataM := range vs.List() { + streamData := streamDataM.(map[string]interface{}) + var stream librato.SpaceChartStream + if v, ok := streamData["metric"].(string); ok && v != "" { + stream.Metric = librato.String(v) + } + if v, ok := streamData["source"].(string); ok && v != "" { + stream.Source = librato.String(v) + } + if v, ok := streamData["composite"].(string); ok && v != "" { + stream.Composite = librato.String(v) + } + if v, ok := streamData["group_function"].(string); ok && v != "" { + stream.GroupFunction = librato.String(v) + } + if v, ok := streamData["summary_function"].(string); ok && v != "" { + stream.SummaryFunction = librato.String(v) + } + if v, ok := streamData["transform_function"].(string); ok && v != "" { + stream.TransformFunction = librato.String(v) + } + if v, ok := streamData["color"].(string); ok && v != "" { + stream.Color = librato.String(v) + } + if v, ok := streamData["units_short"].(string); ok && v != "" { + stream.UnitsShort = librato.String(v) + } + if v, ok := streamData["units_longs"].(string); ok && v != "" { + stream.UnitsLong = librato.String(v) + } + if v, ok := streamData["min"].(float64); ok && !math.IsNaN(v) { + stream.Min = librato.Float(v) + } + if v, ok := streamData["max"].(float64); ok && !math.IsNaN(v) { + stream.Max = librato.Float(v) + } + streams[i] = stream + } + spaceChart.Streams = streams + } + + spaceChartResult, _, err := client.Spaces.CreateChart(spaceID, spaceChart) + if err != nil { + return fmt.Errorf("Error creating Librato space chart %s: %s", *spaceChart.Name, err) + } + + resource.Retry(1*time.Minute, func() *resource.RetryError { + _, _, err := client.Spaces.GetChart(spaceID, *spaceChartResult.ID) + if err != nil { + if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + + return resourceLibratoSpaceChartReadResult(d, spaceChartResult) +} + +func resourceLibratoSpaceChartRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*librato.Client) + + spaceID := uint(d.Get("space_id").(int)) + + id, err := strconv.ParseUint(d.Id(), 10, 0) + if err != nil { + return err + } + + chart, _, err := client.Spaces.GetChart(spaceID, uint(id)) + if err != nil { + if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 { + d.SetId("") + return nil + } + return fmt.Errorf("Error reading Librato Space chart %s: %s", d.Id(), err) + } + + return resourceLibratoSpaceChartReadResult(d, chart) +} + +func resourceLibratoSpaceChartReadResult(d *schema.ResourceData, chart *librato.SpaceChart) error { + d.SetId(strconv.FormatUint(uint64(*chart.ID), 10)) + if chart.Name != nil { + if err := d.Set("name", *chart.Name); err != nil { + return err + } + } + if chart.Type != nil { + if err := d.Set("type", *chart.Type); err != nil { + return err + } + } + if chart.Min != nil { + if err := d.Set("min", *chart.Min); err != nil { + return err + } + } + if chart.Max != nil { + if err := d.Set("max", *chart.Max); err != nil { + return err + } + } + if chart.Label != nil { + if err := d.Set("label", *chart.Label); err != nil { + return err + } + } + if chart.RelatedSpace != nil { + if err := d.Set("related_space", *chart.RelatedSpace); err != nil { + return err + } + } + + streams := resourceLibratoSpaceChartStreamsGather(d, chart.Streams) + if err := d.Set("stream", streams); err != nil { + return err + } + + return nil +} + +func resourceLibratoSpaceChartStreamsGather(d *schema.ResourceData, streams []librato.SpaceChartStream) []map[string]interface{} { + retStreams := make([]map[string]interface{}, 0, len(streams)) + for _, s := range streams { + stream := make(map[string]interface{}) + if s.Metric != nil { + stream["metric"] = *s.Metric + } + if s.Source != nil { + stream["source"] = *s.Source + } + if s.Composite != nil { + stream["composite"] = *s.Composite + } + if s.GroupFunction != nil { + stream["group_function"] = *s.GroupFunction + } + if s.SummaryFunction != nil { + stream["summary_function"] = *s.SummaryFunction + } + if s.TransformFunction != nil { + stream["transform_function"] = *s.TransformFunction + } + if s.Color != nil { + stream["color"] = *s.Color + } + if s.UnitsShort != nil { + stream["units_short"] = *s.UnitsShort + } + if s.UnitsLong != nil { + stream["units_long"] = *s.UnitsLong + } + retStreams = append(retStreams, stream) + } + + return retStreams +} + +func resourceLibratoSpaceChartUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*librato.Client) + + spaceID := uint(d.Get("space_id").(int)) + chartID, err := strconv.ParseUint(d.Id(), 10, 0) + if err != nil { + return err + } + + spaceChart := new(librato.SpaceChart) + if d.HasChange("name") { + spaceChart.Name = librato.String(d.Get("name").(string)) + } + if d.HasChange("min") { + if math.IsNaN(d.Get("min").(float64)) { + spaceChart.Min = nil + } else { + spaceChart.Min = librato.Float(d.Get("min").(float64)) + } + } + if d.HasChange("max") { + if math.IsNaN(d.Get("max").(float64)) { + spaceChart.Max = nil + } else { + spaceChart.Max = librato.Float(d.Get("max").(float64)) + } + } + if d.HasChange("label") { + spaceChart.Label = librato.String(d.Get("label").(string)) + } + if d.HasChange("related_space") { + spaceChart.RelatedSpace = librato.Uint(d.Get("related_space").(uint)) + } + if d.HasChange("stream") { + vs := d.Get("stream").(*schema.Set) + streams := make([]librato.SpaceChartStream, vs.Len()) + for i, streamDataM := range vs.List() { + streamData := streamDataM.(map[string]interface{}) + var stream librato.SpaceChartStream + if v, ok := streamData["metric"].(string); ok && v != "" { + stream.Metric = librato.String(v) + } + if v, ok := streamData["source"].(string); ok && v != "" { + stream.Source = librato.String(v) + } + if v, ok := streamData["composite"].(string); ok && v != "" { + stream.Composite = librato.String(v) + } + if v, ok := streamData["group_function"].(string); ok && v != "" { + stream.GroupFunction = librato.String(v) + } + if v, ok := streamData["summary_function"].(string); ok && v != "" { + stream.SummaryFunction = librato.String(v) + } + if v, ok := streamData["transform_function"].(string); ok && v != "" { + stream.TransformFunction = librato.String(v) + } + if v, ok := streamData["color"].(string); ok && v != "" { + stream.Color = librato.String(v) + } + if v, ok := streamData["units_short"].(string); ok && v != "" { + stream.UnitsShort = librato.String(v) + } + if v, ok := streamData["units_longs"].(string); ok && v != "" { + stream.UnitsLong = librato.String(v) + } + if v, ok := streamData["min"].(float64); ok && !math.IsNaN(v) { + stream.Min = librato.Float(v) + } + if v, ok := streamData["max"].(float64); ok && !math.IsNaN(v) { + stream.Max = librato.Float(v) + } + streams[i] = stream + } + spaceChart.Streams = streams + } + + _, err = client.Spaces.EditChart(spaceID, uint(chartID), spaceChart) + if err != nil { + return fmt.Errorf("Error updating Librato space chart %s: %s", *spaceChart.Name, err) + } + + return resourceLibratoSpaceChartRead(d, meta) +} + +func resourceLibratoSpaceChartDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*librato.Client) + + spaceID := uint(d.Get("space_id").(int)) + + id, err := strconv.ParseUint(d.Id(), 10, 0) + if err != nil { + return err + } + + log.Printf("[INFO] Deleting Chart: %d/%d", spaceID, uint(id)) + _, err = client.Spaces.DeleteChart(spaceID, uint(id)) + if err != nil { + return fmt.Errorf("Error deleting space: %s", err) + } + + resource.Retry(1*time.Minute, func() *resource.RetryError { + _, _, err := client.Spaces.GetChart(spaceID, uint(id)) + if err != nil { + if errResp, ok := err.(*librato.ErrorResponse); ok && errResp.Response.StatusCode == 404 { + return nil + } + return resource.NonRetryableError(err) + } + return resource.RetryableError(fmt.Errorf("space chart still exists")) + }) + + d.SetId("") + return nil +} diff --git a/builtin/providers/librato/resource_librato_space_chart_test.go b/builtin/providers/librato/resource_librato_space_chart_test.go new file mode 100644 index 000000000..8fe01b7ec --- /dev/null +++ b/builtin/providers/librato/resource_librato_space_chart_test.go @@ -0,0 +1,230 @@ +package librato + +import ( + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/henrikhodne/go-librato/librato" +) + +func TestAccLibratoSpaceChart_Basic(t *testing.T) { + var spaceChart librato.SpaceChart + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibratoSpaceChartDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckLibratoSpaceChartConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLibratoSpaceChartExists("librato_space_chart.foobar", &spaceChart), + testAccCheckLibratoSpaceChartName(&spaceChart, "Foo Bar"), + resource.TestCheckResourceAttr( + "librato_space_chart.foobar", "name", "Foo Bar"), + ), + }, + }, + }) +} + +func TestAccLibratoSpaceChart_Full(t *testing.T) { + var spaceChart librato.SpaceChart + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibratoSpaceChartDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckLibratoSpaceChartConfig_full, + Check: resource.ComposeTestCheckFunc( + testAccCheckLibratoSpaceChartExists("librato_space_chart.foobar", &spaceChart), + testAccCheckLibratoSpaceChartName(&spaceChart, "Foo Bar"), + resource.TestCheckResourceAttr( + "librato_space_chart.foobar", "name", "Foo Bar"), + ), + }, + }, + }) +} + +func TestAccLibratoSpaceChart_Updated(t *testing.T) { + var spaceChart librato.SpaceChart + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibratoSpaceChartDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckLibratoSpaceChartConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLibratoSpaceChartExists("librato_space_chart.foobar", &spaceChart), + testAccCheckLibratoSpaceChartName(&spaceChart, "Foo Bar"), + resource.TestCheckResourceAttr( + "librato_space_chart.foobar", "name", "Foo Bar"), + ), + }, + resource.TestStep{ + Config: testAccCheckLibratoSpaceChartConfig_new_value, + Check: resource.ComposeTestCheckFunc( + testAccCheckLibratoSpaceChartExists("librato_space_chart.foobar", &spaceChart), + testAccCheckLibratoSpaceChartName(&spaceChart, "Bar Baz"), + resource.TestCheckResourceAttr( + "librato_space_chart.foobar", "name", "Bar Baz"), + ), + }, + }, + }) +} + +func testAccCheckLibratoSpaceChartDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*librato.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "librato_space_chart" { + continue + } + + id, err := strconv.ParseUint(rs.Primary.ID, 10, 0) + if err != nil { + return fmt.Errorf("ID not a number") + } + + spaceID, err := strconv.ParseUint(rs.Primary.Attributes["space_id"], 10, 0) + if err != nil { + return fmt.Errorf("Space ID not a number") + } + + _, _, err = client.Spaces.GetChart(uint(spaceID), uint(id)) + + if err == nil { + return fmt.Errorf("Space Chart still exists") + } + } + + return nil +} + +func testAccCheckLibratoSpaceChartName(spaceChart *librato.SpaceChart, name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if spaceChart.Name == nil || *spaceChart.Name != name { + return fmt.Errorf("Bad name: %s", *spaceChart.Name) + } + + return nil + } +} + +func testAccCheckLibratoSpaceChartExists(n string, spaceChart *librato.SpaceChart) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Space Chart ID is set") + } + + client := testAccProvider.Meta().(*librato.Client) + + id, err := strconv.ParseUint(rs.Primary.ID, 10, 0) + if err != nil { + return fmt.Errorf("ID not a number") + } + + spaceID, err := strconv.ParseUint(rs.Primary.Attributes["space_id"], 10, 0) + if err != nil { + return fmt.Errorf("Space ID not a number") + } + + foundSpaceChart, _, err := client.Spaces.GetChart(uint(spaceID), uint(id)) + + if err != nil { + return err + } + + if foundSpaceChart.ID == nil || *foundSpaceChart.ID != uint(id) { + return fmt.Errorf("Space not found") + } + + *spaceChart = *foundSpaceChart + + return nil + } +} + +const testAccCheckLibratoSpaceChartConfig_basic = ` +resource "librato_space" "foobar" { + name = "Foo Bar" +} + +resource "librato_space_chart" "foobar" { + space_id = "${librato_space.foobar.id}" + name = "Foo Bar" + type = "line" +}` + +const testAccCheckLibratoSpaceChartConfig_new_value = ` +resource "librato_space" "foobar" { + name = "Foo Bar" +} + +resource "librato_space_chart" "foobar" { + space_id = "${librato_space.foobar.id}" + name = "Bar Baz" + type = "line" +}` + +const testAccCheckLibratoSpaceChartConfig_full = ` +resource "librato_space" "foobar" { + name = "Foo Bar" +} + +resource "librato_space" "barbaz" { + name = "Bar Baz" +} + +resource "librato_space_chart" "foobar" { + space_id = "${librato_space.foobar.id}" + name = "Foo Bar" + type = "line" + min = 0 + max = 100 + label = "Percent" + related_space = "${librato_space.barbaz.id}" + + # Minimal metric stream + stream { + metric = "cpu" + source = "*" + } + + # Minimal composite stream + stream { + composite = "s(\"cpu\", \"*\")" + } + + # Full metric stream + stream { + metric = "cpu" + source = "*" + group_function = "average" + summary_function = "max" + name = "CPU usage" + color = "#990000" + units_short = "%" + units_long = "percent" + min = 0 + max = 100 + transform_function = "x * 100" + period = 60 + } +}` diff --git a/builtin/providers/librato/resource_librato_space_test.go b/builtin/providers/librato/resource_librato_space_test.go new file mode 100644 index 000000000..ce055ccd2 --- /dev/null +++ b/builtin/providers/librato/resource_librato_space_test.go @@ -0,0 +1,106 @@ +package librato + +import ( + "fmt" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/henrikhodne/go-librato/librato" +) + +func TestAccLibratoSpace_Basic(t *testing.T) { + var space librato.Space + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLibratoSpaceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccCheckLibratoSpaceConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLibratoSpaceExists("librato_space.foobar", &space), + testAccCheckLibratoSpaceAttributes(&space), + resource.TestCheckResourceAttr( + "librato_space.foobar", "name", "Foo Bar"), + ), + }, + }, + }) +} + +func testAccCheckLibratoSpaceDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*librato.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "librato_space" { + continue + } + + id, err := strconv.ParseUint(rs.Primary.ID, 10, 0) + if err != nil { + return fmt.Errorf("ID not a number") + } + + _, _, err = client.Spaces.Get(uint(id)) + + if err == nil { + return fmt.Errorf("Space still exists") + } + } + + return nil +} + +func testAccCheckLibratoSpaceAttributes(space *librato.Space) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if space.Name == nil || *space.Name != "Foo Bar" { + return fmt.Errorf("Bad name: %s", *space.Name) + } + + return nil + } +} + +func testAccCheckLibratoSpaceExists(n string, space *librato.Space) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Space ID is set") + } + + client := testAccProvider.Meta().(*librato.Client) + + id, err := strconv.ParseUint(rs.Primary.ID, 10, 0) + if err != nil { + return fmt.Errorf("ID not a number") + } + + foundSpace, _, err := client.Spaces.Get(uint(id)) + + if err != nil { + return err + } + + if foundSpace.ID == nil || *foundSpace.ID != uint(id) { + return fmt.Errorf("Space not found") + } + + *space = *foundSpace + + return nil + } +} + +const testAccCheckLibratoSpaceConfig_basic = ` +resource "librato_space" "foobar" { + name = "Foo Bar" +}` diff --git a/vendor/github.com/henrikhodne/go-librato/LICENSE b/vendor/github.com/henrikhodne/go-librato/LICENSE new file mode 100644 index 000000000..1255f582f --- /dev/null +++ b/vendor/github.com/henrikhodne/go-librato/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright 2015 Henrik Hodne + +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/henrikhodne/go-librato/librato/client.go b/vendor/github.com/henrikhodne/go-librato/librato/client.go new file mode 100644 index 000000000..181e2c7f5 --- /dev/null +++ b/vendor/github.com/henrikhodne/go-librato/librato/client.go @@ -0,0 +1,282 @@ +package librato + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "reflect" + + "github.com/google/go-querystring/query" +) + +const ( + libraryVersion = "0.1" + defaultBaseURL = "https://metrics-api.librato.com/v1/" + userAgent = "go-librato/" + libraryVersion + + defaultMediaType = "application/json" +) + +// A Client manages communication with the Librato API. +type Client struct { + // HTTP client used to communicate with the API + client *http.Client + + // Headers to attach to every request made with the client. Headers will be + // used to provide Librato API authentication details and other necessary + // headers. + Headers map[string]string + + // Email and Token contains the authentication details needed to authenticate + // against the Librato API. + Email, Token string + + // Base URL for API requests. Defaults to the public Librato API, but can be + // set to an alternate endpoint if necessary. BaseURL should always be + // terminated by a slash. + BaseURL *url.URL + + // User agent used when communicating with the Librato API. + UserAgent string + + // Services used to manipulate API entities. + Spaces *SpacesService +} + +// NewClient returns a new Librato API client bound to the public Librato API. +func NewClient(email, token string) *Client { + bu, err := url.Parse(defaultBaseURL) + if err != nil { + panic("Default Librato API base URL couldn't be parsed") + } + + return NewClientWithBaseURL(bu, email, token) +} + +// NewClientWithBaseURL returned a new Librato API client with a custom base URL. +func NewClientWithBaseURL(baseURL *url.URL, email, token string) *Client { + headers := map[string]string{ + "Content-Type": defaultMediaType, + "Accept": defaultMediaType, + } + + c := &Client{ + client: http.DefaultClient, + Headers: headers, + Email: email, + Token: token, + BaseURL: baseURL, + UserAgent: userAgent, + } + + c.Spaces = &SpacesService{client: c} + + return c +} + +// NewRequest creates an API request. A relative URL can be provided in urlStr, +// in which case it is resolved relative to the BaseURL of the Client. +// Relative URLs should always be specified without a preceding slash. If +// specified, the value pointed to by body is JSON encoded and included as the +// request body. If specified, the map provided by headers will be used to +// update request headers. +func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) { + rel, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + u := c.BaseURL.ResolveReference(rel) + + var buf io.ReadWriter + if body != nil { + buf = new(bytes.Buffer) + err := json.NewEncoder(buf).Encode(body) + if err != nil { + return nil, err + } + } + + req, err := http.NewRequest(method, u.String(), buf) + if err != nil { + return nil, err + } + + req.SetBasicAuth(c.Email, c.Token) + if c.UserAgent != "" { + req.Header.Set("User-Agent", c.UserAgent) + } + + for k, v := range c.Headers { + req.Header.Set(k, v) + } + + return req, nil +} + +// Do sends an API request and returns the API response. The API response is +// JSON decoded and stored in the value pointed to by v, or returned as an +// error if an API error has occurred. If v implements the io.Writer +// interface, the raw response body will be written to v, without attempting to +// first decode it. +func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) { + resp, err := c.client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + err = CheckResponse(resp) + if err != nil { + return resp, err + } + + if v != nil { + if w, ok := v.(io.Writer); ok { + _, err = io.Copy(w, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(v) + } + } + + return resp, err +} + +// ErrorResponse reports an error caused by an API request. +// ErrorResponse implements the Error interface. +type ErrorResponse struct { + // HTTP response that caused this error + Response *http.Response + + // Error messages produces by Librato API. + Errors ErrorResponseMessages `json:"errors"` +} + +func (er *ErrorResponse) Error() string { + buf := new(bytes.Buffer) + + if er.Errors.Params != nil && len(er.Errors.Params) > 0 { + buf.WriteString(" Parameter errors:") + for param, errs := range er.Errors.Params { + fmt.Fprintf(buf, " %s:", param) + for _, err := range errs { + fmt.Fprintf(buf, " %s,", err) + } + } + buf.WriteString(".") + } + + if er.Errors.Request != nil && len(er.Errors.Request) > 0 { + buf.WriteString(" Request errors:") + for _, err := range er.Errors.Request { + fmt.Fprintf(buf, " %s,", err) + } + buf.WriteString(".") + } + + if er.Errors.System != nil && len(er.Errors.System) > 0 { + buf.WriteString(" System errors:") + for _, err := range er.Errors.System { + fmt.Fprintf(buf, " %s,", err) + } + buf.WriteString(".") + } + + return fmt.Sprintf( + "%v %v: %d %v", + er.Response.Request.Method, + er.Response.Request.URL, + er.Response.StatusCode, + buf.String(), + ) +} + +// ErrorResponseMessages contains error messages returned from the Librato API. +type ErrorResponseMessages struct { + Params map[string][]string `json:"params,omitempty"` + Request []string `json:"request,omitempty"` + System []string `json:"system,omitempty"` +} + +// CheckResponse checks the API response for errors; and returns them if +// present. A Response is considered an error if it has a status code outside +// the 2XX range. +func CheckResponse(r *http.Response) error { + if c := r.StatusCode; 200 <= c && c <= 299 { + return nil + } + + errorResponse := &ErrorResponse{Response: r} + + data, err := ioutil.ReadAll(r.Body) + if err == nil && data != nil { + json.Unmarshal(data, errorResponse) + } + + return errorResponse +} + +func urlWithOptions(s string, opt interface{}) (string, error) { + rv := reflect.ValueOf(opt) + if rv.Kind() == reflect.Ptr && rv.IsNil() { + return s, nil + } + + u, err := url.Parse(s) + if err != nil { + return s, err + } + + qs, err := query.Values(opt) + if err != nil { + return "", err + } + u.RawQuery = qs.Encode() + + return u.String(), nil +} + +// Bool is a helper routine that allocates a new bool value +// to store v and returns a pointer to it. +func Bool(v bool) *bool { + p := new(bool) + *p = v + return p +} + +// Int is a helper routine that allocates a new int32 value +// to store v and returns a pointer to it, but unlike Int32 +// its argument value is an int. +func Int(v int) *int { + p := new(int) + *p = v + return p +} + +// Uint is a helper routine that allocates a new uint value +// to store v and returns a pointer to it. +func Uint(v uint) *uint { + p := new(uint) + *p = v + return p +} + +// String is a helper routine that allocates a new string value +// to store v and returns a pointer to it. +func String(v string) *string { + p := new(string) + *p = v + return p +} + +// Float is a helper routine that allocates a new float64 value +// to store v and returns a pointer to it. +func Float(v float64) *float64 { + p := new(float64) + *p = v + return p +} diff --git a/vendor/github.com/henrikhodne/go-librato/librato/spaces.go b/vendor/github.com/henrikhodne/go-librato/librato/spaces.go new file mode 100644 index 000000000..6a003dfbe --- /dev/null +++ b/vendor/github.com/henrikhodne/go-librato/librato/spaces.go @@ -0,0 +1,123 @@ +package librato + +import ( + "fmt" + "net/http" +) + +// SpacesService handles communication with the Librato API methods related to +// spaces. +type SpacesService struct { + client *Client +} + +// Space represents a Librato Space. +type Space struct { + Name *string `json:"name"` + ID *uint `json:"id,omitempty"` +} + +func (s Space) String() string { + return Stringify(s) +} + +// SpaceListOptions specifies the optional parameters to the SpaceService.Find +// method. +type SpaceListOptions struct { + // filter by name + Name string `url:"name,omitempty"` +} + +type listSpacesResponse struct { + Spaces []Space `json:"spaces"` +} + +// List spaces using the provided options. +// +// Librato API docs: http://dev.librato.com/v1/get/spaces +func (s *SpacesService) List(opt *SpaceListOptions) ([]Space, *http.Response, error) { + u, err := urlWithOptions("spaces", opt) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var spacesResp listSpacesResponse + resp, err := s.client.Do(req, &spacesResp) + if err != nil { + return nil, resp, err + } + + return spacesResp.Spaces, resp, nil +} + +// Get fetches a space based on the provided ID. +// +// Librato API docs: http://dev.librato.com/v1/get/spaces/:id +func (s *SpacesService) Get(id uint) (*Space, *http.Response, error) { + u, err := urlWithOptions(fmt.Sprintf("spaces/%d", id), nil) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + sp := new(Space) + resp, err := s.client.Do(req, sp) + if err != nil { + return nil, resp, err + } + + return sp, resp, err +} + +// Create a space with a given name. +// +// Librato API docs: http://dev.librato.com/v1/post/spaces +func (s *SpacesService) Create(space *Space) (*Space, *http.Response, error) { + req, err := s.client.NewRequest("POST", "spaces", space) + if err != nil { + return nil, nil, err + } + + sp := new(Space) + resp, err := s.client.Do(req, sp) + if err != nil { + return nil, resp, err + } + + return sp, resp, err +} + +// Edit a space. +// +// Librato API docs: http://dev.librato.com/v1/put/spaces/:id +func (s *SpacesService) Edit(spaceID uint, space *Space) (*http.Response, error) { + u := fmt.Sprintf("spaces/%d", spaceID) + req, err := s.client.NewRequest("PUT", u, space) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// Delete a space. +// +// Librato API docs: http://dev.librato.com/v1/delete/spaces/:id +func (s *SpacesService) Delete(id uint) (*http.Response, error) { + u := fmt.Sprintf("spaces/%d", id) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/henrikhodne/go-librato/librato/spaces_charts.go b/vendor/github.com/henrikhodne/go-librato/librato/spaces_charts.go new file mode 100644 index 000000000..fe8f68973 --- /dev/null +++ b/vendor/github.com/henrikhodne/go-librato/librato/spaces_charts.go @@ -0,0 +1,118 @@ +package librato + +import ( + "fmt" + "net/http" +) + +// SpaceChart represents a chart in a Librato Space. +type SpaceChart struct { + ID *uint `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + Type *string `json:"type,omitempty"` + Min *float64 `json:"min,omitempty"` + Max *float64 `json:"max,omitempty"` + Label *string `json:"label,omitempty"` + RelatedSpace *uint `json:"related_space,omitempty"` + Streams []SpaceChartStream `json:"streams,omitempty"` +} + +// SpaceChartStream represents a single stream in a chart in a Librato Space. +type SpaceChartStream struct { + Metric *string `json:"metric,omitempty"` + Source *string `json:"source,omitempty"` + Composite *string `json:"composite,omitempty"` + GroupFunction *string `json:"group_function,omitempty"` + SummaryFunction *string `json:"summary_function,omitempty"` + Color *string `json:"color,omitempty"` + Name *string `json:"name,omitempty"` + UnitsShort *string `json:"units_short,omitempty"` + UnitsLong *string `json:"units_long,omitempty"` + Min *float64 `json:"min,omitempty"` + Max *float64 `json:"max,omitempty"` + TransformFunction *string `json:"transform_function,omitempty"` + Period *int64 `json:"period,omitempty"` +} + +// CreateChart creates a chart in a given Librato Space. +// +// Librato API docs: http://dev.librato.com/v1/post/spaces/:id/charts +func (s *SpacesService) CreateChart(spaceID uint, chart *SpaceChart) (*SpaceChart, *http.Response, error) { + u := fmt.Sprintf("spaces/%d/charts", spaceID) + req, err := s.client.NewRequest("POST", u, chart) + if err != nil { + return nil, nil, err + } + + c := new(SpaceChart) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// ListCharts lists all charts in a given Librato Space. +// +// Librato API docs: http://dev.librato.com/v1/get/spaces/:id/charts +func (s *SpacesService) ListCharts(spaceID uint) ([]SpaceChart, *http.Response, error) { + u := fmt.Sprintf("spaces/%d/charts", spaceID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + charts := new([]SpaceChart) + resp, err := s.client.Do(req, charts) + if err != nil { + return nil, resp, err + } + + return *charts, resp, err +} + +// GetChart gets a chart with a given ID in a space with a given ID. +// +// Librato API docs: http://dev.librato.com/v1/get/spaces/:id/charts +func (s *SpacesService) GetChart(spaceID, chartID uint) (*SpaceChart, *http.Response, error) { + u := fmt.Sprintf("spaces/%d/charts/%d", spaceID, chartID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + c := new(SpaceChart) + resp, err := s.client.Do(req, c) + if err != nil { + return nil, resp, err + } + + return c, resp, err +} + +// EditChart edits a chart. +// +// Librato API docs: http://dev.librato.com/v1/put/spaces/:id/charts/:id +func (s *SpacesService) EditChart(spaceID, chartID uint, chart *SpaceChart) (*http.Response, error) { + u := fmt.Sprintf("spaces/%d/charts/%d", spaceID, chartID) + req, err := s.client.NewRequest("PUT", u, chart) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} + +// DeleteChart deletes a chart. +// +// Librato API docs: http://dev.librato.com/v1/delete/spaces/:id/charts/:id +func (s *SpacesService) DeleteChart(spaceID, chartID uint) (*http.Response, error) { + u := fmt.Sprintf("spaces/%d/charts/%d", spaceID, chartID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + + return s.client.Do(req, nil) +} diff --git a/vendor/github.com/henrikhodne/go-librato/librato/strings.go b/vendor/github.com/henrikhodne/go-librato/librato/strings.go new file mode 100644 index 000000000..953bf86e8 --- /dev/null +++ b/vendor/github.com/henrikhodne/go-librato/librato/strings.go @@ -0,0 +1,79 @@ +package librato + +import ( + "bytes" + "fmt" + "io" + "reflect" +) + +// Stringify attempts to create a reasonable string representation of types in +// the Librato library. It does things like resolve pointers to their values +// and omits struct fields with nil values. +func Stringify(message interface{}) string { + var buf bytes.Buffer + v := reflect.ValueOf(message) + stringifyValue(&buf, v) + return buf.String() +} + +// stringifyValue was heavily inspired by the goprotobuf library. + +func stringifyValue(w io.Writer, val reflect.Value) { + if val.Kind() == reflect.Ptr && val.IsNil() { + w.Write([]byte("")) + return + } + + v := reflect.Indirect(val) + + switch v.Kind() { + case reflect.String: + fmt.Fprintf(w, `"%s"`, v) + case reflect.Slice: + w.Write([]byte{'['}) + for i := 0; i < v.Len(); i++ { + if i > 0 { + w.Write([]byte{' '}) + } + + stringifyValue(w, v.Index(i)) + } + + w.Write([]byte{']'}) + return + case reflect.Struct: + if v.Type().Name() != "" { + w.Write([]byte(v.Type().String())) + } + + w.Write([]byte{'{'}) + + var sep bool + for i := 0; i < v.NumField(); i++ { + fv := v.Field(i) + if fv.Kind() == reflect.Ptr && fv.IsNil() { + continue + } + if fv.Kind() == reflect.Slice && fv.IsNil() { + continue + } + + if sep { + w.Write([]byte(", ")) + } else { + sep = true + } + + w.Write([]byte(v.Type().Field(i).Name)) + w.Write([]byte{':'}) + stringifyValue(w, fv) + } + + w.Write([]byte{'}'}) + default: + if v.CanInterface() { + fmt.Fprint(w, v.Interface()) + } + } +} diff --git a/vendor/github.com/henrikhodne/go-librato/librato/testing_helpers.go b/vendor/github.com/henrikhodne/go-librato/librato/testing_helpers.go new file mode 100644 index 000000000..feaca2564 --- /dev/null +++ b/vendor/github.com/henrikhodne/go-librato/librato/testing_helpers.go @@ -0,0 +1,17 @@ +package librato + +import ( + "fmt" + "path/filepath" + "runtime" + "testing" +) + +// ok fails the test if an err is not nil. +func ok(tb testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) + tb.FailNow() + } +} diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index 3c78bd3c3..a238b1c5c 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -27,6 +27,7 @@ body.layout-fastly, body.layout-google, body.layout-heroku, body.layout-influxdb, +body.layout-librato, body.layout-mailgun, body.layout-mysql, body.layout-openstack, diff --git a/website/source/docs/providers/librato/index.html.markdown b/website/source/docs/providers/librato/index.html.markdown new file mode 100644 index 000000000..4874f4d27 --- /dev/null +++ b/website/source/docs/providers/librato/index.html.markdown @@ -0,0 +1,39 @@ +--- +layout: "librato" +page_title: "Provider: Librato" +sidebar_current: "docs-librato-index" +description: |- + The Librato provider is used to interact with the resources supported by Librato. The provider needs to be configured with the proper credentials before it can be used. +--- + +# Librato Provider + +The Librato provider is used to interact with the +resources supported by Librato. 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 Librato provider +provider "librato" { + email = "ops@company.com" + token = "${var.librato_token}" +} + +# Create a new space +resource "librato_space" "default" { + ... +} +``` + +## Argument Reference + +The following arguments are supported: + +* `token` - (Required) Librato API token. It must be provided, but it can also + be sourced from the `LIBRATO_TOKEN` environment variable. +* `email` - (Required) Librato email address. It must be provided, but it can + also be sourced from the `LIBRATO_EMAIL` environment variable. diff --git a/website/source/docs/providers/librato/r/space.html.markdown b/website/source/docs/providers/librato/r/space.html.markdown new file mode 100644 index 000000000..44317be0d --- /dev/null +++ b/website/source/docs/providers/librato/r/space.html.markdown @@ -0,0 +1,34 @@ +--- +layout: "librato" +page_title: "Librato: librato_space" +sidebar_current: "docs-librato-resource-space" +description: |- + Provides a Librato Space resource. This can be used to create and manage spaces on Librato. +--- + +# librato\_space + +Provides a Librato Space resource. This can be used to +create and manage spaces on Librato. + +## Example Usage + +``` +# Create a new Librato space +resource "librato_space" "default" { + name = "My New Space" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the space. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the space. +* `name` - The name of the space. diff --git a/website/source/docs/providers/librato/r/space_chart.html.markdown b/website/source/docs/providers/librato/r/space_chart.html.markdown new file mode 100644 index 000000000..ad9b20fa9 --- /dev/null +++ b/website/source/docs/providers/librato/r/space_chart.html.markdown @@ -0,0 +1,110 @@ +--- +layout: "librato" +page_title: "Librato: librato_space_chart" +sidebar_current: "docs-librato-resource-space-chart" +description: |- + Provides a Librato Space Chart resource. This can be used to create and manage charts in Librato Spaces. +--- + +# librato\_space\_chart + +Provides a Librato Space Chart resource. This can be used to +create and manage charts in Librato Spaces. + +## Example Usage + +``` +# Create a new Librato space +resource "librato_space" "my_space" { + name = "My New Space" +} + +# Create a new chart +resource "librato_space_chart" "server_temperature" { + name = "Server Temperature" + space_id = "${librato_space.my_space.id}" + + stream { + metric = "server_temp" + source = "app1" + } + + stream { + metric = "environmental_temp" + source = "*" + group_function = "breakout" + summary_function = "average" + } + + stream { + metric = "server_temp" + source = "%" + group_function = "average" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `space_id` - (Required) The ID of the space this chart should be in. +* `name` - (Required) The title of the chart when it is displayed. +* `type` - (Optional) Indicates the type of chart. Must be one of line or + stacked (default to line). +* `min` - (Optional) The minimum display value of the chart's Y-axis. +* `max` - (Optional) The maximum display value of the chart's Y-axis. +* `label` - (Optional) The Y-axis label. +* `related_space` - (Optional) The ID of another space to which this chart is + related. +* `stream` - (Optional) Nested block describing a metric to use for data in the + chart. The structure of this block is described below. + +The `stream` block supports: + +* `metric` - (Required) The name of the metric. May not be specified if + `composite` is specified. +* `source` - (Required) The name of a source, or `*` to include all sources. + This field will also accept specific wildcard entries. For example + us-west-\*-app will match us-west-21-app but not us-west-12-db. Use % to + specify a dynamic source that will be provided after the instrument or + dashboard has loaded, or in the URL. May not be specified if `composite` is + specified. +* `group_function` - (Required) How to process the results when multiple sources + will be returned. Value must be one of average, sum, breakout. If average or + sum, a single line will be drawn representing the average or sum + (respectively) of all sources. If the group_function is breakout, a separate + line will be drawn for each source. If this property is not supplied, the + behavior will default to average. May not be specified if `composite` is + specified. +* `composite` - (Required) A composite metric query string to execute when this + stream is displayed. May not be specified if `metric`, `source` or + `group_function` is specified. +* `summary_function` - (Optional) When visualizing complex measurements or a + rolled-up measurement, this allows you to choose which statistic to use. + Defaults to "average". Valid options are: "max", "min", "average", "sum" or + "count". +* `name` - (Optional) A display name to use for the stream when generating the + tooltip. +* `color` - (Optional) Sets a color to use when rendering the stream. Must be a + seven character string that represents the hex code of the color e.g. + "#52D74C". +* `units_short` - (Optional) Unit value string to use as the tooltip label. +* `units_long` - (Optional) String value to set as they Y-axis label. All + streams that share the same units_long value will be plotted on the same + Y-axis. +* `min` - (Optional) Theoretical minimum Y-axis value. +* `max` - (Optional) Theoretical maximum Y-axis value. +* `transform_function` - (Optional) Linear formula to run on each measurement + prior to visualizaton. +* `period` - (Optional) An integer value of seconds that defines the period this + stream reports at. This aids in the display of the stream and allows the + period to be used in stream display transforms. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the chart. +* `space_id` - The ID of the space this chart should be in. +* `title` - The title of the chart when it is displayed. diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 3a32f5c88..462fcc34f 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -215,6 +215,10 @@ > InfluxDB + + + > + Librato > diff --git a/website/source/layouts/librato.erb b/website/source/layouts/librato.erb new file mode 100644 index 000000000..cbd6f718f --- /dev/null +++ b/website/source/layouts/librato.erb @@ -0,0 +1,29 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> + <% end %>