diff --git a/builtin/bins/provider-logentries/main.go b/builtin/bins/provider-logentries/main.go new file mode 100644 index 0000000000..fcd1a5c3f5 --- /dev/null +++ b/builtin/bins/provider-logentries/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/logentries" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: logentries.Provider, + }) +} diff --git a/builtin/bins/provider-logentries/main_test.go b/builtin/bins/provider-logentries/main_test.go new file mode 100644 index 0000000000..06ab7d0f9a --- /dev/null +++ b/builtin/bins/provider-logentries/main_test.go @@ -0,0 +1 @@ +package main diff --git a/builtin/providers/logentries/expect/expect.go b/builtin/providers/logentries/expect/expect.go new file mode 100644 index 0000000000..71858ce957 --- /dev/null +++ b/builtin/providers/logentries/expect/expect.go @@ -0,0 +1,81 @@ +package test + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "reflect" + "regexp" +) + +type TestExistsCheckFactoryFunc func(resource string, fact interface{}) resource.TestCheckFunc + +type TestExpectValue interface { + Execute(val interface{}) error + String() string +} + +func TestCheckResourceExpectation(res string, fact interface{}, existsFunc TestExistsCheckFactoryFunc, expectation map[string]TestExpectValue) resource.TestCheckFunc { + return func(s *terraform.State) error { + if err := existsFunc(res, fact)(s); err != nil { + return fmt.Errorf("Expectation existence check error: %s", err) + } + + value := reflect.ValueOf(fact).Elem() + for i := 0; i < value.NumField(); i++ { + t := value.Type().Field(i).Tag + tv := t.Get("tfresource") + + // TODO: Support data types other than string + fv := value.Field(i).Interface().(string) + if expect, ok := expectation[tv]; ok { + if err := expect.Execute(fv); err != nil { + return fmt.Errorf("Expected %s, got \"%s\" (for %s)", expectation[tv], fv, tv) + } + } + } + return nil + } +} + +type RegexTestExpectValue struct { + Value interface{} + TestExpectValue +} + +func (t *RegexTestExpectValue) Execute(val interface{}) error { + expr := t.Value.(string) + if !regexp.MustCompile(expr).MatchString(val.(string)) { + return fmt.Errorf("Expected regexp match for \"%s\": %s", expr, val) + } + return nil +} + +func (t *RegexTestExpectValue) String() string { + return fmt.Sprintf("regex[%s]", t.Value.(string)) +} + +func RegexMatches(exp string) TestExpectValue { + return &RegexTestExpectValue{Value: exp} +} + +type EqualsTestExpectValue struct { + Value interface{} + TestExpectValue +} + +func (t *EqualsTestExpectValue) Execute(val interface{}) error { + expr := t.Value.(string) + if val.(string) != t.Value.(string) { + return fmt.Errorf("Expected %s and %s to be equal", expr, val) + } + return nil +} + +func (t *EqualsTestExpectValue) String() string { + return fmt.Sprintf("equals[%s]", t.Value.(string)) +} + +func Equals(exp string) TestExpectValue { + return &EqualsTestExpectValue{Value: exp} +} diff --git a/builtin/providers/logentries/provider.go b/builtin/providers/logentries/provider.go new file mode 100644 index 0000000000..f687c4791d --- /dev/null +++ b/builtin/providers/logentries/provider.go @@ -0,0 +1,42 @@ +package logentries + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" + "github.com/logentries/le_goclient" +) + +// Provider returns a terraform.ResourceProvider. +func Provider() terraform.ResourceProvider { + + // The actual provider + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "account_key": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("LOGENTRIES_ACCOUNT_KEY", nil), + Description: descriptions["account_key"], + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "logentries_log": resourceLogentriesLog(), + "logentries_logset": resourceLogentriesLogSet(), + }, + + ConfigureFunc: providerConfigure, + } +} + +var descriptions map[string]string + +func init() { + descriptions = map[string]string{ + "account_key": "The Log Entries account key.", + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + return logentries.NewClient(d.Get("account_key").(string)), nil +} diff --git a/builtin/providers/logentries/provider_test.go b/builtin/providers/logentries/provider_test.go new file mode 100644 index 0000000000..bc2b9319fa --- /dev/null +++ b/builtin/providers/logentries/provider_test.go @@ -0,0 +1,34 @@ +package logentries + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" + "os" + "testing" +) + +var testAccProviders map[string]terraform.ResourceProvider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider().(*schema.Provider) + testAccProviders = map[string]terraform.ResourceProvider{ + "logentries": 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("LOGENTRIES_ACCOUNT_KEY"); v == "" { + t.Fatal("LOGENTRIES_ACCOUNT_KEY must be set for acceptance tests") + } +} diff --git a/builtin/providers/logentries/resource_logentries_log.go b/builtin/providers/logentries/resource_logentries_log.go new file mode 100644 index 0000000000..f5db7ecfce --- /dev/null +++ b/builtin/providers/logentries/resource_logentries_log.go @@ -0,0 +1,235 @@ +package logentries + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/schema" + logentries "github.com/logentries/le_goclient" + "strconv" +) + +func resourceLogentriesLog() *schema.Resource { + + return &schema.Resource{ + Create: resourceLogentriesLogCreate, + Read: resourceLogentriesLogRead, + Update: resourceLogentriesLogUpdate, + Delete: resourceLogentriesLogDelete, + + Schema: map[string]*schema.Schema{ + "token": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + ForceNew: true, + }, + "logset_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "filename": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "retention_period": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "ACCOUNT_DEFAULT", + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + allowed_values := []string{"1W", "2W", "1M", "2M", "6M", "1Y", "2Y", "UNLIMITED", "ACCOUNT_DEFAULT"} + if !sliceContains(value, allowed_values) { + errors = append(errors, fmt.Errorf("Invalid retention period: %s (must be one of: %s)", value, allowed_values)) + } + return + }, + }, + "source": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "token", + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + allowed_values := []string{"token", "syslog", "agent", "api"} + if !sliceContains(value, allowed_values) { + errors = append(errors, fmt.Errorf("Invalid log source option: %s (must be one of: %s)", value, allowed_values)) + } + return + }, + }, + "type": &schema.Schema{ + Type: schema.TypeString, + Default: "", + Optional: true, + }, + }, + } +} + +func resourceLogentriesLogCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*logentries.Client) + retentionPeriod, err := retentionPeriodForEnum(d.Get("retention_period").(string)) + if err != nil { + return err + } + res, err := client.Log.Create(logentries.LogCreateRequest{ + LogSetKey: d.Get("logset_id").(string), + Name: d.Get("name").(string), + Retention: strconv.FormatInt(retentionPeriod, 10), + Type: d.Get("type").(string), + Source: d.Get("source").(string), + Filename: d.Get("filename").(string), + }) + + if err != nil { + return err + } + + d.SetId(res.Key) + + return mapLogToSchema(client, res, d) +} + +func resourceLogentriesLogRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*logentries.Client) + res, err := client.Log.Read(logentries.LogReadRequest{ + LogSetKey: d.Get("logset_id").(string), + Key: d.Id(), + }) + if err != nil { + return err + } + + if res == nil { + d.SetId("") + return nil + } + + return mapLogToSchema(client, res, d) +} + +func resourceLogentriesLogUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*logentries.Client) + _, err := client.Log.Update(logentries.LogUpdateRequest{ + Key: d.Id(), + Name: d.Get("name").(string), + Retention: d.Get("retention_period").(string), + Type: d.Get("type").(string), + Source: d.Get("source").(string), + Filename: d.Get("filename").(string), + }) + if err != nil { + return err + } + + return resourceLogentriesLogRead(d, meta) +} + +func resourceLogentriesLogDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*logentries.Client) + err := client.Log.Delete(logentries.LogDeleteRequest{ + LogSetKey: d.Get("logset_id").(string), + Key: d.Id(), + }) + return err +} + +func mapLogToSchema(client *logentries.Client, log *logentries.Log, d *schema.ResourceData) error { + d.Set("token", log.Token) + d.Set("name", log.Name) + d.Set("filename", log.Filename) + retentionEnum, err := enumForRetentionPeriod(log.Retention) + if err != nil { + return err + } + d.Set("retention_period", retentionEnum) + d.Set("source", log.Source) + if log.Type != "" { + logTypes, err := client.LogType.ReadDefault(logentries.LogTypeListRequest{}) + if err != nil { + return err + } + logType := lookupTypeShortcut(log.Type, logTypes) + if logType == "" { + logTypes, err = client.LogType.Read(logentries.LogTypeListRequest{}) + if err != nil { + return err + } + logType = lookupTypeShortcut(log.Type, logTypes) + } + d.Set("type", logType) + } + + return nil +} + +func enumForRetentionPeriod(retentionPeriod int64) (string, error) { + switch retentionPeriod { + case 604800000: + return "1W", nil + case 1209600000: + return "2W", nil + case 2678400000: + return "1M", nil + case 5356800000: + return "2M", nil + case 16070400000: + return "6M", nil + case 31536000000: + return "1Y", nil + case 63072000000: + return "2Y", nil + case 0: + return "UNLIMITED", nil + case -1: + return "ACCOUNT_DEFAULT", nil + } + + return "", fmt.Errorf("Unknown retention period: %d", retentionPeriod) +} + +func retentionPeriodForEnum(retentionPeriodEnum string) (int64, error) { + switch retentionPeriodEnum { + case "1W": + return 604800000, nil + case "2W": + return 1209600000, nil + case "1M": + return 2678400000, nil + case "2M": + return 5356800000, nil + case "6M": + return 16070400000, nil + case "1Y": + return 31536000000, nil + case "2Y": + return 63072000000, nil + case "UNLIMITED": + return 0, nil + case "ACCOUNT_DEFAULT": + return -1, nil + } + + return 0, fmt.Errorf("Unknown retention period: %s", retentionPeriodEnum) +} + +func lookupTypeShortcut(currentLogKey string, logTypes []logentries.LogType) string { + for _, logType := range logTypes { + if logType.Key == currentLogKey { + return logType.Shortcut + } + } + return "" +} + +func sliceContains(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} diff --git a/builtin/providers/logentries/resource_logentries_log_test.go b/builtin/providers/logentries/resource_logentries_log_test.go new file mode 100644 index 0000000000..ea1305e68b --- /dev/null +++ b/builtin/providers/logentries/resource_logentries_log_test.go @@ -0,0 +1,310 @@ +package logentries + +import ( + "fmt" + lexp "github.com/hashicorp/terraform/builtin/providers/logentries/expect" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/logentries/le_goclient" + "testing" +) + +type LogResource struct { + Name string `tfresource:"name"` + RetentionPeriod string `tfresource:"retention_period"` + Source string `tfresource:"source"` + Token string `tfresource:"token"` + Type string `tfresource:"type"` +} + +func TestAccLogentriesLog_Token(t *testing.T) { + var logResource LogResource + + logName := fmt.Sprintf("terraform-test-%s", acctest.RandString(8)) + testAccLogentriesLogConfig := fmt.Sprintf(` + resource "logentries_logset" "test_logset" { + name = "%s" + } + resource "logentries_log" "test_log" { + logset_id = "${logentries_logset.test_logset.id}" + name = "%s" + } + `, logName, logName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(s *terraform.State) error { + testAccCheckLogentriesLogDestroy(s) + testAccCheckLogentriesLogSetDestroy(s) + return nil + }, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccLogentriesLogConfig, + Check: lexp.TestCheckResourceExpectation( + "logentries_log.test_log", + &logResource, + testAccCheckLogentriesLogExists, + map[string]lexp.TestExpectValue{ + "name": lexp.Equals(logName), + "source": lexp.Equals("token"), + "token": lexp.RegexMatches("[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}"), + }, + ), + }, + }, + }) +} + +func TestAccLogentriesLog_SourceApi(t *testing.T) { + var logResource LogResource + + logName := fmt.Sprintf("terraform-test-%s", acctest.RandString(8)) + testAccLogentriesLogConfig := fmt.Sprintf(` + resource "logentries_logset" "test_logset" { + name = "%s" + } + resource "logentries_log" "test_log" { + logset_id = "${logentries_logset.test_logset.id}" + name = "%s" + source = "api" + } + `, logName, logName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(s *terraform.State) error { + testAccCheckLogentriesLogDestroy(s) + testAccCheckLogentriesLogSetDestroy(s) + return nil + }, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccLogentriesLogConfig, + Check: lexp.TestCheckResourceExpectation( + "logentries_log.test_log", + &logResource, + testAccCheckLogentriesLogExists, + map[string]lexp.TestExpectValue{ + "name": lexp.Equals(logName), + "source": lexp.Equals("api"), + }, + ), + }, + }, + }) +} + +func TestAccLogentriesLog_SourceAgent(t *testing.T) { + var logResource LogResource + + logName := fmt.Sprintf("terraform-test-%s", acctest.RandString(8)) + fileName := "/opt/foo" + testAccLogentriesLogConfig := fmt.Sprintf(` + resource "logentries_logset" "test_logset" { + name = "%s" + } + resource "logentries_log" "test_log" { + logset_id = "${logentries_logset.test_logset.id}" + name = "%s" + source = "agent" + filename = "%s" + } + `, logName, logName, fileName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(s *terraform.State) error { + testAccCheckLogentriesLogDestroy(s) + testAccCheckLogentriesLogSetDestroy(s) + return nil + }, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccLogentriesLogConfig, + Check: lexp.TestCheckResourceExpectation( + "logentries_log.test_log", + &logResource, + testAccCheckLogentriesLogExists, + map[string]lexp.TestExpectValue{ + "name": lexp.Equals(logName), + "source": lexp.Equals("agent"), + "filename": lexp.Equals(fileName), + }, + ), + }, + }, + }) +} + +func TestAccLogentriesLog_RetentionPeriod1M(t *testing.T) { + var logResource LogResource + + logName := fmt.Sprintf("terraform-test-%s", acctest.RandString(8)) + testAccLogentriesLogConfig := fmt.Sprintf(` + resource "logentries_logset" "test_logset" { + name = "%s" + } + resource "logentries_log" "test_log" { + logset_id = "${logentries_logset.test_logset.id}" + name = "%s" + retention_period = "1M" + } + `, logName, logName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(s *terraform.State) error { + testAccCheckLogentriesLogDestroy(s) + testAccCheckLogentriesLogSetDestroy(s) + return nil + }, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccLogentriesLogConfig, + Check: lexp.TestCheckResourceExpectation( + "logentries_log.test_log", + &logResource, + testAccCheckLogentriesLogExists, + map[string]lexp.TestExpectValue{ + "name": lexp.Equals(logName), + "retention_period": lexp.Equals("1M"), + }, + ), + }, + }, + }) +} + +func TestAccLogentriesLog_RetentionPeriodAccountDefault(t *testing.T) { + var logResource LogResource + + logName := fmt.Sprintf("terraform-test-%s", acctest.RandString(8)) + testAccLogentriesLogConfig := fmt.Sprintf(` + resource "logentries_logset" "test_logset" { + name = "%s" + } + resource "logentries_log" "test_log" { + logset_id = "${logentries_logset.test_logset.id}" + name = "%s" + } + `, logName, logName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(s *terraform.State) error { + testAccCheckLogentriesLogDestroy(s) + testAccCheckLogentriesLogSetDestroy(s) + return nil + }, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccLogentriesLogConfig, + Check: lexp.TestCheckResourceExpectation( + "logentries_log.test_log", + &logResource, + testAccCheckLogentriesLogExists, + map[string]lexp.TestExpectValue{ + "name": lexp.Equals(logName), + "retention_period": lexp.Equals("ACCOUNT_DEFAULT"), + }, + ), + }, + }, + }) +} + +func TestAccLogentriesLog_RetentionPeriodAccountUnlimited(t *testing.T) { + var logResource LogResource + + logName := fmt.Sprintf("terraform-test-%s", acctest.RandString(8)) + testAccLogentriesLogConfig := fmt.Sprintf(` + resource "logentries_logset" "test_logset" { + name = "%s" + } + resource "logentries_log" "test_log" { + logset_id = "${logentries_logset.test_logset.id}" + name = "%s" + retention_period = "UNLIMITED" + } + `, logName, logName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(s *terraform.State) error { + testAccCheckLogentriesLogDestroy(s) + testAccCheckLogentriesLogSetDestroy(s) + return nil + }, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccLogentriesLogConfig, + Check: lexp.TestCheckResourceExpectation( + "logentries_log.test_log", + &logResource, + testAccCheckLogentriesLogExists, + map[string]lexp.TestExpectValue{ + "name": lexp.Equals(logName), + "retention_period": lexp.Equals("UNLIMITED"), + }, + ), + }, + }, + }) +} + +func testAccCheckLogentriesLogDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*logentries.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "logentries_logset" { + continue + } + + resp, err := client.Log.Read(logentries.LogReadRequest{Key: rs.Primary.ID}) + + if err == nil { + return fmt.Errorf("Log still exists: %#v", resp) + } + } + + return nil +} + +func testAccCheckLogentriesLogExists(n string, fact interface{}) 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 LogSet Key is set") + } + + client := testAccProvider.Meta().(*logentries.Client) + + resp, err := client.Log.Read(logentries.LogReadRequest{Key: rs.Primary.ID}) + + if err != nil { + return err + } + + res := fact.(*LogResource) + res.Name = resp.Name + res.RetentionPeriod, _ = enumForRetentionPeriod(resp.Retention) + res.Source = resp.Source + res.Token = resp.Token + res.Type = resp.Type + + return nil + } +} diff --git a/builtin/providers/logentries/resource_logentries_logset.go b/builtin/providers/logentries/resource_logentries_logset.go new file mode 100644 index 0000000000..d120769531 --- /dev/null +++ b/builtin/providers/logentries/resource_logentries_logset.go @@ -0,0 +1,84 @@ +package logentries + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/logentries/le_goclient" +) + +func resourceLogentriesLogSet() *schema.Resource { + + return &schema.Resource{ + Create: resourceLogentriesLogSetCreate, + Read: resourceLogentriesLogSetRead, + Update: resourceLogentriesLogSetUpdate, + Delete: resourceLogentriesLogSetDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "location": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "nonlocation", + }, + }, + } +} + +func resourceLogentriesLogSetCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*logentries.Client) + res, err := client.LogSet.Create(logentries.LogSetCreateRequest{ + Name: d.Get("name").(string), + Location: d.Get("location").(string), + }) + + if err != nil { + return err + } + + d.SetId(res.Key) + + return resourceLogentriesLogSetRead(d, meta) +} + +func resourceLogentriesLogSetRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*logentries.Client) + res, err := client.LogSet.Read(logentries.LogSetReadRequest{ + Key: d.Id(), + }) + if err != nil { + return err + } + + if res == nil { + d.SetId("") + return nil + } + + d.Set("location", res.Location) + return nil +} + +func resourceLogentriesLogSetUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*logentries.Client) + _, err := client.LogSet.Update(logentries.LogSetUpdateRequest{ + Key: d.Id(), + Name: d.Get("name").(string), + Location: d.Get("location").(string), + }) + if err != nil { + return err + } + + return resourceLogentriesLogRead(d, meta) +} + +func resourceLogentriesLogSetDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*logentries.Client) + err := client.LogSet.Delete(logentries.LogSetDeleteRequest{ + Key: d.Id(), + }) + return err +} diff --git a/builtin/providers/logentries/resource_logentries_logset_test.go b/builtin/providers/logentries/resource_logentries_logset_test.go new file mode 100644 index 0000000000..30762a7f31 --- /dev/null +++ b/builtin/providers/logentries/resource_logentries_logset_test.go @@ -0,0 +1,125 @@ +package logentries + +import ( + "fmt" + lexp "github.com/hashicorp/terraform/builtin/providers/logentries/expect" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/logentries/le_goclient" + "testing" +) + +type LogSetResource struct { + Name string `tfresource:"name"` + Location string `tfresource:"location"` +} + +func TestAccLogentriesLogSet_Basic(t *testing.T) { + var logSetResource LogSetResource + + logSetName := fmt.Sprintf("terraform-test-%s", acctest.RandString(8)) + testAccLogentriesLogSetConfig := fmt.Sprintf(` + resource "logentries_logset" "test_logset" { + name = "%s" + location = "terraform.io" + } + `, logSetName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLogentriesLogSetDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccLogentriesLogSetConfig, + Check: lexp.TestCheckResourceExpectation( + "logentries_logset.test_logset", + &logSetResource, + testAccCheckLogentriesLogSetExists, + map[string]lexp.TestExpectValue{ + "name": lexp.Equals(logSetName), + "location": lexp.Equals("terraform.io"), + }, + ), + }, + }, + }) +} + +func TestAccLogentriesLogSet_NoLocation(t *testing.T) { + var logSetResource LogSetResource + + logSetName := fmt.Sprintf("terraform-test-%s", acctest.RandString(8)) + testAccLogentriesLogSetConfig := fmt.Sprintf(` + resource "logentries_logset" "test_logset" { + name = "%s" + } + `, logSetName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLogentriesLogSetDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccLogentriesLogSetConfig, + Check: lexp.TestCheckResourceExpectation( + "logentries_logset.test_logset", + &logSetResource, + testAccCheckLogentriesLogSetExists, + map[string]lexp.TestExpectValue{ + "name": lexp.Equals(logSetName), + "location": lexp.Equals("nonlocation"), + }, + ), + }, + }, + }) +} + +func testAccCheckLogentriesLogSetDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*logentries.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "logentries_logset" { + continue + } + + resp, err := client.LogSet.Read(logentries.LogSetReadRequest{Key: rs.Primary.ID}) + + if err == nil { + return fmt.Errorf("Log set still exists: %#v", resp) + } + } + + return nil +} + +func testAccCheckLogentriesLogSetExists(resource string, fact interface{}) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resource] + + if !ok { + return fmt.Errorf("Not found: %s", resource) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No LogSet Key is set") + } + + client := testAccProvider.Meta().(*logentries.Client) + + resp, err := client.LogSet.Read(logentries.LogSetReadRequest{Key: rs.Primary.ID}) + + if err != nil { + return err + } + + res := fact.(*LogSetResource) + res.Location = resp.Location + res.Name = resp.Name + + return nil + } +} diff --git a/command/internal_plugin_list.go b/command/internal_plugin_list.go index 7e9b246c86..4ce91b4091 100644 --- a/command/internal_plugin_list.go +++ b/command/internal_plugin_list.go @@ -29,6 +29,7 @@ import ( herokuprovider "github.com/hashicorp/terraform/builtin/providers/heroku" influxdbprovider "github.com/hashicorp/terraform/builtin/providers/influxdb" libratoprovider "github.com/hashicorp/terraform/builtin/providers/librato" + logentriesprovider "github.com/hashicorp/terraform/builtin/providers/logentries" mailgunprovider "github.com/hashicorp/terraform/builtin/providers/mailgun" mysqlprovider "github.com/hashicorp/terraform/builtin/providers/mysql" nullprovider "github.com/hashicorp/terraform/builtin/providers/null" @@ -81,6 +82,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{ "heroku": herokuprovider.Provider, "influxdb": influxdbprovider.Provider, "librato": libratoprovider.Provider, + "logentries": logentriesprovider.Provider, "mailgun": mailgunprovider.Provider, "mysql": mysqlprovider.Provider, "null": nullprovider.Provider, diff --git a/vendor/github.com/logentries/le_goclient/LICENSE.md b/vendor/github.com/logentries/le_goclient/LICENSE.md new file mode 100644 index 0000000000..bc68f24c37 --- /dev/null +++ b/vendor/github.com/logentries/le_goclient/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2016 Logentries + +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/logentries/le_goclient/README.md b/vendor/github.com/logentries/le_goclient/README.md new file mode 100644 index 0000000000..b468cdd6de --- /dev/null +++ b/vendor/github.com/logentries/le_goclient/README.md @@ -0,0 +1,23 @@ +# Logentries client (golang) +Provides the capability to perform CRUD operations on log sets, logs, and log types. + +# Example + +``` +package main + +import ( + "fmt" + logentries "github.com/logentries/le_goclient" +) + +func main() { + client := logentries.NewClient("") + res, err := client.User.Read(logentries.UserReadRequest{}) + fmt.Printf("err: %s\n", err) + fmt.Println(res) +} +``` + +# License +See LICENSE.md diff --git a/vendor/github.com/logentries/le_goclient/client.go b/vendor/github.com/logentries/le_goclient/client.go new file mode 100644 index 0000000000..0bb4763919 --- /dev/null +++ b/vendor/github.com/logentries/le_goclient/client.go @@ -0,0 +1,67 @@ +package logentries + +type ApiResponse struct { + Response string `json:"response"` + ResponseReason string `json:"reason"` + Worker string `json:"worker"` + Id string `json:"id"` +} + +type ApiObject struct { + Object string `json:"object"` +} + +type LogType struct { + Title string `json:"title"` + Description string `json:"desc"` + Key string `json:"key"` + Shortcut string `json:"shortcut"` + ApiObject +} + +type Log struct { + Name string `json:"name"` + Created int64 `json:"created"` + Key string `json:"key"` + Token string `json:"token"` + Follow string `json:"follow"` + Retention int64 `json:"retention"` + Source string `json:"type"` + Type string `json:"logtype"` + Filename string `json:"filename"` + ApiObject +} + +type LogSet struct { + Distver string `json:"distver"` + C int64 `json:"c"` + Name string `json:"name"` + Distname string `json:"distname"` + Location string `json:"hostname"` + Key string `json:"key"` + Logs []Log + ApiObject +} + +type User struct { + UserKey string `json:"user_key"` + LogSets []LogSet `json:"hosts"` + Apps []interface{} `json:"apps"` + Logs []interface{} `json:"logs"` +} + +type Client struct { + Log *LogClient + LogSet *LogSetClient + User *UserClient + LogType *LogTypeClient +} + +func NewClient(account_key string) *Client { + client := &Client{} + client.Log = NewLogClient(account_key) + client.LogSet = NewLogSetClient(account_key) + client.User = NewUserClient(account_key) + client.LogType = NewLogTypeClient(account_key) + return client +} diff --git a/vendor/github.com/logentries/le_goclient/log.go b/vendor/github.com/logentries/le_goclient/log.go new file mode 100644 index 0000000000..d561f864c6 --- /dev/null +++ b/vendor/github.com/logentries/le_goclient/log.go @@ -0,0 +1,182 @@ +package logentries + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" +) + +type LogClient struct { + AccountKey string +} + +type LogCreateRequest struct { + LogSetKey string + Name string + Retention string + Source string + Type string + Filename string +} + +type LogCreateResponse struct { + Key string `json:"log_key"` + Worker string `json:"worker"` + Log Log `json:"log"` + ApiResponse +} + +func (l *LogClient) Create(createRequest LogCreateRequest) (*Log, error) { + form := url.Values{} + form.Add("request", "new_log") + form.Add("user_key", l.AccountKey) + form.Add("host_key", createRequest.LogSetKey) + form.Add("name", createRequest.Name) + form.Add("type", createRequest.Type) + form.Add("filename", createRequest.Filename) + form.Add("retention", createRequest.Retention) + form.Add("source", createRequest.Source) + resp, err := http.PostForm("https://api.logentries.com/", form) + + if err != nil { + return nil, err + } + + if resp.StatusCode == 200 { + var response LogCreateResponse + json.NewDecoder(resp.Body).Decode(&response) + if response.Response == "ok" { + return &response.Log, nil + } else { + return nil, fmt.Errorf("failed to create log %s: %s", createRequest.Name, response.ResponseReason) + } + } + + body, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("Could not retrieve log %s: %s", createRequest.Name, string(body)) +} + +type LogReadRequest struct { + LogSetKey string + Key string +} + +type LogReadResponse struct { + Log Log `json:"log"` + ApiResponse +} + +func (l *LogClient) Read(readRequest LogReadRequest) (*Log, error) { + form := url.Values{} + form.Add("request", "get_log") + form.Add("log_key", readRequest.Key) + resp, err := http.PostForm("https://api.logentries.com/", form) + + if err != nil { + return nil, err + } + + if resp.StatusCode == 200 { + var response LogReadResponse + json.NewDecoder(resp.Body).Decode(&response) + if response.Response == "ok" { + return &response.Log, nil + } else { + return nil, fmt.Errorf("failed to get log %s: %s", readRequest.Key, response.ResponseReason) + } + } + + body, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("Could not retrieve log %s: %s", readRequest.Key, string(body)) +} + +type LogUpdateRequest struct { + Key string + Name string + Type string + Source string + Retention string + Filename string +} + +type LogUpdateResponse struct { + Key string `json:"log_key"` + Worker string `json:"worker"` + Log Log `json:"log"` + ApiResponse +} + +func (l *LogClient) Update(updateRequest LogUpdateRequest) (*Log, error) { + form := url.Values{} + form.Add("request", "set_log") + form.Add("user_key", l.AccountKey) + form.Add("log_key", updateRequest.Key) + form.Add("name", updateRequest.Name) + form.Add("type", updateRequest.Type) + form.Add("source", updateRequest.Source) + form.Add("filename", updateRequest.Filename) + form.Add("retention", updateRequest.Retention) + resp, err := http.PostForm("https://api.logentries.com/", form) + + if err != nil { + return nil, err + } + + if resp.StatusCode == 200 { + var response LogUpdateResponse + json.NewDecoder(resp.Body).Decode(&response) + if response.Response == "ok" { + return &response.Log, nil + } else { + return nil, fmt.Errorf("failed to update log %s: %s", updateRequest.Name, response.ResponseReason) + } + } + + body, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("Could not retrieve log %s: %s", updateRequest.Name, string(body)) +} + +type LogDeleteRequest struct { + LogSetKey string + Key string +} + +type LogDeleteResponse struct { + LogSetKey string `json:"host_key"` + UserKey string `json:"user_key"` + Key string `json:"log_key"` + Worker string `json:"worker"` + ApiResponse +} + +func (l *LogClient) Delete(deleteRequest LogDeleteRequest) error { + form := url.Values{} + form.Add("request", "rm_log") + form.Add("user_key", l.AccountKey) + form.Add("host_key", deleteRequest.LogSetKey) + form.Add("log_key", deleteRequest.Key) + resp, err := http.PostForm("https://api.logentries.com/", form) + + if err != nil { + return err + } + + if resp.StatusCode == 200 { + var deleteResponse LogDeleteResponse + json.NewDecoder(resp.Body).Decode(&deleteResponse) + if deleteResponse.Response == "ok" { + return nil + } else { + return fmt.Errorf("failed to delete log %s: %s", deleteResponse.Key, deleteResponse.ResponseReason) + } + } + + return fmt.Errorf("failed to delete log %s: %s", deleteRequest.Key, resp.Body) +} + +func NewLogClient(account_key string) *LogClient { + log := &LogClient{AccountKey: account_key} + return log +} diff --git a/vendor/github.com/logentries/le_goclient/logset.go b/vendor/github.com/logentries/le_goclient/logset.go new file mode 100644 index 0000000000..9012006763 --- /dev/null +++ b/vendor/github.com/logentries/le_goclient/logset.go @@ -0,0 +1,162 @@ +package logentries + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" +) + +type LogSetClient struct { + AccountKey string +} + +type LogSetCreateRequest struct { + Name string + Location string + DistVer string + System string + DistName string +} + +type LogSetCreateResponse struct { + AgentKey string `json:"agent_key"` + HostKey string `json:"host_key"` + LogSet `json:"host"` + ApiResponse +} + +func (l *LogSetClient) Create(createRequest LogSetCreateRequest) (*LogSet, error) { + form := url.Values{} + form.Add("request", "register") + form.Add("user_key", l.AccountKey) + form.Add("name", createRequest.Name) + form.Add("hostname", createRequest.Location) + form.Add("distver", createRequest.DistVer) + form.Add("system", createRequest.System) + form.Add("distname", createRequest.DistName) + resp, err := http.PostForm("https://api.logentries.com/", form) + + if err != nil { + return nil, err + } + + if resp.StatusCode == 200 { + var response LogSetCreateResponse + json.NewDecoder(resp.Body).Decode(&response) + if response.Response == "ok" { + return &response.LogSet, nil + } else { + return nil, fmt.Errorf("failed to create log %s: %s", createRequest.Name, response.ResponseReason) + } + } + + body, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("Could not retrieve log %s: %s", createRequest.Name, string(body)) +} + +type LogSetReadRequest struct { + Key string +} + +type LogSetReadResponse struct { + LogSet LogSet + ApiResponse +} + +func (l *LogSetClient) Read(readRequest LogSetReadRequest) (*LogSet, error) { + userClient := NewUserClient(l.AccountKey) + response, err := userClient.Read(UserReadRequest{}) + if err != nil { + return nil, err + } + + for _, logSet := range response.LogSets { + if logSet.Key == readRequest.Key { + return &logSet, nil + } + } + + return nil, fmt.Errorf("No such log set with key %s", readRequest.Key) +} + +type LogSetUpdateRequest struct { + Key string + Name string + Location string +} + +type LogSetUpdateResponse struct { + AgentKey string `json:"agent_key"` + Key string `json:"host_key"` + LogSet `json:"host"` + ApiResponse +} + +func (l *LogSetClient) Update(updateRequest LogSetUpdateRequest) (*LogSet, error) { + form := url.Values{} + form.Add("request", "set_host") + form.Add("user_key", l.AccountKey) + form.Add("host_key", updateRequest.Key) + form.Add("name", updateRequest.Name) + form.Add("hostname", string(updateRequest.Location)) + resp, err := http.PostForm("https://api.logentries.com/", form) + + if err != nil { + return nil, err + } + + if resp.StatusCode == 200 { + var response LogSetUpdateResponse + json.NewDecoder(resp.Body).Decode(&response) + if response.Response == "ok" { + return &response.LogSet, nil + } else { + return nil, fmt.Errorf("failed to update log set %s: %s", updateRequest.Name, response.ResponseReason) + } + } + + body, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("Could not retrieve log set %s: %s", updateRequest.Name, string(body)) +} + +type LogSetDeleteRequest struct { + Key string +} + +type LogSetDeleteResponse struct { + UserKey string `json:"user_key"` + Key string `json:"host_key"` + Worker string `json:"worker"` + ApiResponse +} + +func (l *LogSetClient) Delete(deleteRequest LogSetDeleteRequest) error { + form := url.Values{} + form.Add("request", "rm_host") + form.Add("user_key", l.AccountKey) + form.Add("host_key", deleteRequest.Key) + resp, err := http.PostForm("https://api.logentries.com/", form) + + if err != nil { + return err + } + + if resp.StatusCode == 200 { + var deleteResponse LogSetDeleteResponse + json.NewDecoder(resp.Body).Decode(&deleteResponse) + if deleteResponse.Response == "ok" { + return nil + } else { + return fmt.Errorf("failed to delete log set %s: %s", deleteResponse.Key, deleteResponse.ResponseReason) + } + } + + return fmt.Errorf("failed to delete log set %s: %s", deleteRequest.Key, resp.Body) +} + +func NewLogSetClient(account_key string) *LogSetClient { + logset := &LogSetClient{AccountKey: account_key} + return logset +} diff --git a/vendor/github.com/logentries/le_goclient/logtype.go b/vendor/github.com/logentries/le_goclient/logtype.go new file mode 100644 index 0000000000..65a256b26a --- /dev/null +++ b/vendor/github.com/logentries/le_goclient/logtype.go @@ -0,0 +1,59 @@ +package logentries + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" +) + +type LogTypeClient struct { + AccountKey string +} + +type LogTypeListRequest struct { +} + +type LogTypeListResponse struct { + List []LogType + ApiResponse +} + +func (u *LogTypeClient) ReadDefault(defaultLogTypeListRequest LogTypeListRequest) ([]LogType, error) { + return u.read("list_logtypes_default", defaultLogTypeListRequest) +} + +func (u *LogTypeClient) Read(defaultLogTypeListRequest LogTypeListRequest) ([]LogType, error) { + return u.read("list_logtypes", defaultLogTypeListRequest) +} + +func (u *LogTypeClient) read(requestType string, logTypeListRequest LogTypeListRequest) ([]LogType, error) { + form := url.Values{} + form.Add("request", requestType) + form.Add("user_key", u.AccountKey) + form.Add("id", "terraform") + resp, err := http.PostForm("https://api.logentries.com/", form) + + if err != nil { + return nil, err + } + + if resp.StatusCode == 200 { + var response LogTypeListResponse + json.NewDecoder(resp.Body).Decode(&response) + if response.Response == "ok" { + return response.List, nil + } else { + return nil, fmt.Errorf("failed to retrieve default log type list: %s", response.ResponseReason) + } + } + + body, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("Could not retrieve default log set info: %s", string(body)) +} + +func NewLogTypeClient(account_key string) *LogTypeClient { + client := LogTypeClient{AccountKey: account_key} + return &client +} diff --git a/vendor/github.com/logentries/le_goclient/user.go b/vendor/github.com/logentries/le_goclient/user.go new file mode 100644 index 0000000000..e863e4f58c --- /dev/null +++ b/vendor/github.com/logentries/le_goclient/user.go @@ -0,0 +1,50 @@ +package logentries + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" +) + +type UserClient struct { + UserKey string +} + +type UserReadRequest struct { +} + +type UserReadResponse struct { + User + ApiResponse +} + +func (u *UserClient) Read(readRequest UserReadRequest) (*UserReadResponse, error) { + form := url.Values{} + form.Add("request", "get_user") + form.Add("load_hosts", "1") + form.Add("load_logs", "1") + form.Add("load_alerts", "0") + form.Add("user_key", u.UserKey) + form.Add("id", "terraform") + resp, err := http.PostForm("https://api.logentries.com/", form) + + if err != nil { + return nil, err + } + + if resp.StatusCode == 200 { + var response UserReadResponse + json.NewDecoder(resp.Body).Decode(&response) + return &response, nil + } + + body, _ := ioutil.ReadAll(resp.Body) + return nil, fmt.Errorf("Could not retrieve account info: %s", string(body)) +} + +func NewUserClient(account_key string) *UserClient { + account := UserClient{UserKey: account_key} + return &account +} diff --git a/vendor/vendor.json b/vendor/vendor.json index cf678605f7..7712dc914a 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -1052,6 +1052,12 @@ "path": "github.com/lib/pq/oid", "revision": "8ad2b298cadd691a77015666a5372eae5dbfac8f" }, + { + "checksumSHA1": "KoB26db2df078Y2UClT7twI+dxg=", + "path": "github.com/logentries/le_goclient", + "revision": "f6d02e2fca401d3550e08a292f54b0efb6a578f0", + "revisionTime": "2016-07-04T14:48:39Z" + }, { "path": "github.com/lusis/go-artifactory/src/artifactory.v401", "revision": "7e4ce345df825841661d1b3ffbb1327083d4a22f" diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index ef7bf7f69f..f4742e4eda 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -30,6 +30,7 @@ body.layout-google, body.layout-heroku, body.layout-influxdb, body.layout-librato, +body.layout-logentries, body.layout-mailgun, body.layout-mysql, body.layout-openstack, diff --git a/website/source/docs/providers/logentries/index.html.markdown b/website/source/docs/providers/logentries/index.html.markdown new file mode 100644 index 0000000000..19854cc0dc --- /dev/null +++ b/website/source/docs/providers/logentries/index.html.markdown @@ -0,0 +1,72 @@ +--- +layout: "logentries" +page_title: "Provider: Logentries" +sidebar_current: "docs-logentries-index" +description: |- + The Logentries provider is used to manage Logentries logs and log sets. Logentries provides live log management and analytics. The provider needs to be configured with a Logentries account key before it can be used. +--- + +# Logentries Provider + +The Logentries provider is used to manage Logentries logs and log sets. Logentries provides live log management and analytics. The provider needs to be configured with a Logentries account key before it can be used. + +Use the navigation to the left to read about the available resources. + +## Example Usage + +``` +# Configure the Logentries provider +provider "logentries" { + account_key = "${var.logentries_account_key}" +} + +# Create a log set +resource "logentries_logset" "host_logs" { + name = "${var.server}-logs" +} + +# Create a log and add it to the log set +resource "logentries_log" "app_log" { + logset_id = "${logentries_logset.host_logs.id}" + name = "myapp-log" + source = "token" +} + +# Add the log token to a cloud-config that can be used by an +# application to send logs to Logentries +resource "aws_launch_configuration" "app_launch_config" { + name_prefix = "myapp-" + image_id = "${var.ami}" + instance_type = "${var.instance_type}" + + user_data = <Librato + > + Logentries + + > Mailgun diff --git a/website/source/layouts/logentries.erb b/website/source/layouts/logentries.erb new file mode 100644 index 0000000000..8b1eb48cc0 --- /dev/null +++ b/website/source/layouts/logentries.erb @@ -0,0 +1,29 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> +<% end %>