provider/logentries: Implementing logentries provider (#7067)

* logentries provider

* logentries vendoring

* logentries docs
This commit is contained in:
Derek Abdine 2016-07-12 06:14:39 -07:00 committed by Paul Stack
parent 4582cbeb5e
commit 7bdc060d24
24 changed files with 1668 additions and 0 deletions

View File

@ -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,
})
}

View File

@ -0,0 +1 @@
package main

View File

@ -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}
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -29,6 +29,7 @@ import (
herokuprovider "github.com/hashicorp/terraform/builtin/providers/heroku" herokuprovider "github.com/hashicorp/terraform/builtin/providers/heroku"
influxdbprovider "github.com/hashicorp/terraform/builtin/providers/influxdb" influxdbprovider "github.com/hashicorp/terraform/builtin/providers/influxdb"
libratoprovider "github.com/hashicorp/terraform/builtin/providers/librato" libratoprovider "github.com/hashicorp/terraform/builtin/providers/librato"
logentriesprovider "github.com/hashicorp/terraform/builtin/providers/logentries"
mailgunprovider "github.com/hashicorp/terraform/builtin/providers/mailgun" mailgunprovider "github.com/hashicorp/terraform/builtin/providers/mailgun"
mysqlprovider "github.com/hashicorp/terraform/builtin/providers/mysql" mysqlprovider "github.com/hashicorp/terraform/builtin/providers/mysql"
nullprovider "github.com/hashicorp/terraform/builtin/providers/null" nullprovider "github.com/hashicorp/terraform/builtin/providers/null"
@ -81,6 +82,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
"heroku": herokuprovider.Provider, "heroku": herokuprovider.Provider,
"influxdb": influxdbprovider.Provider, "influxdb": influxdbprovider.Provider,
"librato": libratoprovider.Provider, "librato": libratoprovider.Provider,
"logentries": logentriesprovider.Provider,
"mailgun": mailgunprovider.Provider, "mailgun": mailgunprovider.Provider,
"mysql": mysqlprovider.Provider, "mysql": mysqlprovider.Provider,
"null": nullprovider.Provider, "null": nullprovider.Provider,

21
vendor/github.com/logentries/le_goclient/LICENSE.md generated vendored Normal file
View File

@ -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.

23
vendor/github.com/logentries/le_goclient/README.md generated vendored Normal file
View File

@ -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("<account_key>")
res, err := client.User.Read(logentries.UserReadRequest{})
fmt.Printf("err: %s\n", err)
fmt.Println(res)
}
```
# License
See LICENSE.md

67
vendor/github.com/logentries/le_goclient/client.go generated vendored Normal file
View File

@ -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
}

182
vendor/github.com/logentries/le_goclient/log.go generated vendored Normal file
View File

@ -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
}

162
vendor/github.com/logentries/le_goclient/logset.go generated vendored Normal file
View File

@ -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
}

59
vendor/github.com/logentries/le_goclient/logtype.go generated vendored Normal file
View File

@ -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
}

50
vendor/github.com/logentries/le_goclient/user.go generated vendored Normal file
View File

@ -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
}

6
vendor/vendor.json vendored
View File

@ -1052,6 +1052,12 @@
"path": "github.com/lib/pq/oid", "path": "github.com/lib/pq/oid",
"revision": "8ad2b298cadd691a77015666a5372eae5dbfac8f" "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", "path": "github.com/lusis/go-artifactory/src/artifactory.v401",
"revision": "7e4ce345df825841661d1b3ffbb1327083d4a22f" "revision": "7e4ce345df825841661d1b3ffbb1327083d4a22f"

View File

@ -30,6 +30,7 @@ body.layout-google,
body.layout-heroku, body.layout-heroku,
body.layout-influxdb, body.layout-influxdb,
body.layout-librato, body.layout-librato,
body.layout-logentries,
body.layout-mailgun, body.layout-mailgun,
body.layout-mysql, body.layout-mysql,
body.layout-openstack, body.layout-openstack,

View File

@ -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 = <<EOF
#cloud-config
write_files:
- content: |
#!/bin/bash -l
export LOGENTRIES_TOKEN=${logentries_log.app_log.token}
run-my-app.sh
path: "/etc/sv/my-app/run"
permissions: 0500
runcmd:
- ln -s /etc/sv/my-app /etc/service/
EOF
iam_instance_profile = "${var.instance_profile}"
lifecycle {
create_before_destroy = true
}
root_block_device {
volume_type = "gp2"
volume_size = "100"
}
}
```
## Argument Reference
The following arguments are supported in the `provider` block:
* `account_key` - (Required) The Logentries account key. This can also be specified with the `LOGENTRIES_ACCOUNT_KEY` environment variable. See the Logentries [account key documentation](https://logentries.com/doc/accountkey/) for more information.

View File

@ -0,0 +1,38 @@
---
layout: "logentries"
page_title: "Logentries: logentries_log"
sidebar_current: "docs-logentries-log"
description: |-
Creates a Logentries log.
---
# logentries\_log
Provides a Logentries log resource.
## Example Usage
```
# 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"
}
```
## Argument Reference
The following arguments are supported:
* `logset_id` - (Required) The id of the `logentries_logset` resource.
* `name` - (Required) The name of the log. The name should be short and descriptive. For example, Apache Access, Hadoop Namenode.
* `retention_period` - (Optional, default `ACCOUNT_DEFAULT`) The retention period (`1W`, `2W`, `1M`, `2M`, `6M`, `1Y`, `2Y`, `UNLIMITED`, `ACCOUNT_DEFAULT`)
* `source` - (Optional, default `token`) The log source (`token`, `syslog`, `agent`, `api`). Review the Logentries [log inputs documentation](https://docs.logentries.com/docs/) for more information.
* `type` - (Optional) The log type. See the Logentries [log type documentation](https://logentries.com/doc/log-types/) for more information.
## Attributes Reference
The following attributes are exported:
* `token` - If the the log `source` is `token`, this value holds the generated log token that is used by logging clients. See the Logentries [token-based input documentation](https://logentries.com/doc/input-token/) for more information.

View File

@ -0,0 +1,28 @@
---
layout: "logentries"
page_title: "Logentries: logentries_logset"
sidebar_current: "docs-logentries-logset"
description: |-
Creates a Logentries logset.
---
# logentries\_logset
Provides a Logentries logset resource. A logset is a collection of `logentries_log` resources.
## Example Usage
```
# Create a log set
resource "logentries_logset" "host_logs" {
name = "${var.server}-logs"
location = "www.example.com"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The log set name, which should be short and descriptive. For example, www, db1.
* `location` - (Optional, default "nonlocation") A location is for your convenience only. You can specify a DNS entry such as web.example.com, IP address or arbitrary comment.

View File

@ -254,6 +254,10 @@
<a href="/docs/providers/librato/index.html">Librato</a> <a href="/docs/providers/librato/index.html">Librato</a>
</li> </li>
<li<%= sidebar_current("docs-providers-logentries") %>>
<a href="/docs/providers/logentries/index.html">Logentries</a>
</li>
<li<%= sidebar_current("docs-providers-mailgun") %>> <li<%= sidebar_current("docs-providers-mailgun") %>>
<a href="/docs/providers/mailgun/index.html">Mailgun</a> <a href="/docs/providers/mailgun/index.html">Mailgun</a>
</li> </li>

View File

@ -0,0 +1,29 @@
<% wrap_layout :inner do %>
<% content_for :sidebar do %>
<div class="docs-sidebar hidden-print affix-top" role="complementary">
<ul class="nav docs-sidenav">
<li<%= sidebar_current("docs-home") %>>
<a href="/docs/providers/index.html">&laquo; Documentation Home</a>
</li>
<li<%= sidebar_current("docs-logentries-index") %>>
<a href="/docs/providers/logentries/index.html">Logentries Provider</a>
</li>
<li<%= sidebar_current(/^docs-logentries-resource/) %>>
<a href="#">Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-logentries-resource-log") %>>
<a href="/docs/providers/logentries/r/log.html">logentries_log</a>
</li>
<li<%= sidebar_current("docs-logentries-resource-logset") %>>
<a href="/docs/providers/logentries/r/logset.html">logentries_logset</a>
</li>
</ul>
</li>
</ul>
</div>
<% end %>
<%= yield %>
<% end %>