Merge pull request #224 from hashicorp/f-mailgun

New Provider: Mailgun
This commit is contained in:
Jack Pearkes 2014-08-25 10:10:54 -07:00
commit b8b012633c
11 changed files with 492 additions and 1 deletions

View File

@ -0,0 +1,34 @@
package mailgun
import (
"log"
"os"
"github.com/pearkes/mailgun"
)
type Config struct {
APIKey string `mapstructure:"api_key"`
}
// Client() returns a new client for accessing mailgun.
//
func (c *Config) Client() (*mailgun.Client, error) {
// If we have env vars set (like in the acc) tests,
// we need to override the values passed in here.
if v := os.Getenv("MAILGUN_API_KEY"); v != "" {
c.APIKey = v
}
// We don't set a domain right away
client, err := mailgun.NewClient(c.APIKey)
if err != nil {
return nil, err
}
log.Printf("[INFO] Mailgun Client configured ")
return client, nil
}

View File

@ -0,0 +1,38 @@
package mailgun
import (
"log"
"github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/mapstructure"
)
// Provider returns a terraform.ResourceProvider.
func Provider() *schema.Provider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"api_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
ResourcesMap: map[string]*schema.Resource{
"mailgun_domain": resourceMailgunDomain(),
},
ConfigureFunc: providerConfigure,
}
}
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
var config Config
configRaw := d.Get("").(map[string]interface{})
if err := mapstructure.Decode(configRaw, &config); err != nil {
return nil, err
}
log.Println("[INFO] Initializing Mailgun client")
return config.Client()
}

View File

@ -0,0 +1,67 @@
package mailgun
import (
"os"
"testing"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
"github.com/pearkes/mailgun"
)
var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider
func init() {
testAccProvider = Provider()
testAccProviders = map[string]terraform.ResourceProvider{
"mailgun": testAccProvider,
}
}
func TestProvider(t *testing.T) {
if err := Provider().InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestProvider_impl(t *testing.T) {
var _ terraform.ResourceProvider = Provider()
}
func TestProviderConfigure(t *testing.T) {
var expectedKey string
if v := os.Getenv("MAILGUN_API_KEY"); v != "" {
expectedKey = v
} else {
expectedKey = "foo"
}
raw := map[string]interface{}{
"api_key": expectedKey,
}
rawConfig, err := config.NewRawConfig(raw)
if err != nil {
t.Fatalf("err: %s", err)
}
rp := Provider()
err = rp.Configure(terraform.NewResourceConfig(rawConfig))
if err != nil {
t.Fatalf("err: %s", err)
}
config := rp.Meta().(*mailgun.Client)
if config.ApiKey != expectedKey {
t.Fatalf("bad: %#v", config)
}
}
func testAccPreCheck(t *testing.T) {
if v := os.Getenv("MAILGUN_API_KEY"); v == "" {
t.Fatal("MAILGUN_API_KEY must be set for acceptance tests")
}
}

View File

@ -0,0 +1,125 @@
package mailgun
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
"github.com/pearkes/mailgun"
)
func resourceMailgunDomain() *schema.Resource {
return &schema.Resource{
Create: resourceMailgunDomainCreate,
Read: resourceMailgunDomainRead,
Delete: resourceMailgunDomainDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"spam_action": &schema.Schema{
Type: schema.TypeString,
Computed: true,
ForceNew: true,
Optional: true,
},
"smtp_password": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Required: true,
},
"smtp_login": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"wildcard": &schema.Schema{
Type: schema.TypeBool,
Computed: true,
ForceNew: true,
Optional: true,
},
},
}
}
func resourceMailgunDomainCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*mailgun.Client)
opts := mailgun.CreateDomain{}
opts.Name = d.Get("name").(string)
opts.SmtpPassword = d.Get("smtp_password").(string)
opts.SpamAction = d.Get("spam_action").(string)
opts.Wildcard = d.Get("wildcard").(bool)
log.Printf("[DEBUG] Domain create configuration: %#v", opts)
domain, err := client.CreateDomain(&opts)
if err != nil {
return err
}
d.SetId(domain)
log.Printf("[INFO] Domain ID: %s", d.Id())
// Retrieve and update state of domain
_, err = resource_mailgin_domain_retrieve(d.Id(), client, d)
if err != nil {
return err
}
return nil
}
func resourceMailgunDomainDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*mailgun.Client)
log.Printf("[INFO] Deleting Domain: %s", d.Id())
// Destroy the domain
err := client.DestroyDomain(d.Id())
if err != nil {
return fmt.Errorf("Error deleting domain: %s", err)
}
return nil
}
func resourceMailgunDomainRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*mailgun.Client)
_, err := resource_mailgin_domain_retrieve(d.Id(), client, d)
if err != nil {
return err
}
return nil
}
func resource_mailgin_domain_retrieve(id string, client *mailgun.Client, d *schema.ResourceData) (*mailgun.Domain, error) {
domain, err := client.RetrieveDomain(id)
if err != nil {
return nil, fmt.Errorf("Error retrieving domain: %s", err)
}
d.Set("name", domain.Name)
d.Set("smtp_password", domain.SmtpPassword)
d.Set("smtp_login", domain.SmtpLogin)
d.Set("wildcard", domain.Wildcard)
d.Set("spam_action", domain.SpamAction)
return &domain, nil
}

View File

@ -0,0 +1,116 @@
package mailgun
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/pearkes/mailgun"
)
func TestAccMailgunDomain_Basic(t *testing.T) {
var domain mailgun.Domain
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckMailgunDomainDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccCheckMailgunDomainConfig_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckMailgunDomainExists("mailgun_domain.foobar", &domain),
testAccCheckMailgunDomainAttributes(&domain),
resource.TestCheckResourceAttr(
"mailgun_domain.foobar", "name", "terraform.example.com"),
resource.TestCheckResourceAttr(
"mailgun_domain.foobar", "spam_action", "disabled"),
resource.TestCheckResourceAttr(
"mailgun_domain.foobar", "smtp_password", "foobar"),
resource.TestCheckResourceAttr(
"mailgun_domain.foobar", "wildcard", "true"),
),
},
},
})
}
func testAccCheckMailgunDomainDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*mailgun.Client)
for _, rs := range s.Resources {
if rs.Type != "mailgun_domain" {
continue
}
_, err := client.RetrieveDomain(rs.ID)
if err == nil {
return fmt.Errorf("Domain still exists")
}
}
return nil
}
func testAccCheckMailgunDomainAttributes(Domain *mailgun.Domain) resource.TestCheckFunc {
return func(s *terraform.State) error {
if Domain.Name != "terraform.example.com" {
return fmt.Errorf("Bad name: %s", Domain.Name)
}
if Domain.SpamAction != "disabled" {
return fmt.Errorf("Bad spam_action: %s", Domain.SpamAction)
}
if Domain.Wildcard != true {
return fmt.Errorf("Bad wildcard: %s", Domain.Wildcard)
}
if Domain.SmtpPassword != "foobar" {
return fmt.Errorf("Bad smtp_password: %s", Domain.SmtpPassword)
}
return nil
}
}
func testAccCheckMailgunDomainExists(n string, Domain *mailgun.Domain) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.ID == "" {
return fmt.Errorf("No Domain ID is set")
}
client := testAccProvider.Meta().(*mailgun.Client)
foundDomain, err := client.RetrieveDomain(rs.ID)
if err != nil {
return err
}
if foundDomain.Name != rs.ID {
return fmt.Errorf("Domain not found")
}
*Domain = foundDomain
return nil
}
}
const testAccCheckMailgunDomainConfig_basic = `
resource "mailgun_domain" "foobar" {
name = "terraform.example.com"
spam_action = "disabled"
smtp_password = "foobar"
wildcard = true
}`

View File

@ -0,0 +1,34 @@
---
layout: "mailgun"
page_title: "Provider: Mailgun"
sidebar_current: "docs-mailgun-index"
---
# Provider
The Mailgun provider is used to interact with the
resources supported by Mailgun. 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 Mailgun provider
provider "mailgun" {
api_key = "${var.mailgun_api_key}"
}
# Create a new domain
resource "mailgun_domain" "default" {
...
}
```
## Argument Reference
The following arguments are supported:
* `api_key` - (Required) Mailgun API key

View File

@ -0,0 +1,43 @@
---
layout: "mailgun"
page_title: "Mailgun: mailgun_domain"
sidebar_current: "docs-mailgun-resource-domain"
---
# mailgun\_domain
Provides a Mailgun App resource. This can be used to
create and manage applications on Mailgun.
## Example Usage
```
# Create a new mailgun domain
resource "mailgun_domain" "default" {
name = "test.example.com"
spam_action = "disabled"
smtp_password = "foobar"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The domain to add to Mailgun
* `smtp_password` - (Required) Password for SMTP authentication
* `spam_action` - (Optional) `disabled` or `tag` Disable, no spam
filtering will occur for inbound messages. Tag, messages
will be tagged wtih a spam header.
* `wildcard` - (Optional) Boolean determines whether
the domain will accept email for sub-domains.
## Attributes Reference
The following attributes are exported:
* `name` - The name of the domain.
* `smtp_login` - The login email for the SMTP server.
* `smtp_password` - The password to the SMTP server.
* `wildcard` - Whether or not the domain will accept email for sub-domains.
* `spam_action` - The spam filtering setting.

View File

@ -99,6 +99,10 @@
<li<%= sidebar_current("docs-providers-heroku") %>>
<a href="/docs/providers/heroku/index.html">Heroku</a>
</li>
<li<%= sidebar_current("docs-providers-mailgun") %>>
<a href="/docs/providers/mailgun/index.html">Mailgun</a>
</li>
</ul>
</li>

View File

@ -0,0 +1,26 @@
<% 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/index.html">&laquo; Documentation Home</a>
</li>
<li<%= sidebar_current("docs-mailgun-index") %>>
<a href="/docs/providers/mailgun/index.html">Mailgun Provider</a>
</li>
<li<%= sidebar_current("docs-mailgun-resource") %>>
<a href="#">Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-mailgun-resource-domain") %>>
<a href="/docs/providers/mailgun/r/domain.html">mailgun_domain</a>
</li>
</ul>
</li>
</ul>
</div>
<% end %>
<%= yield %>
<% end %>

View File

@ -10,6 +10,7 @@ body.layout-consul,
body.layout-dnsimple,
body.layout-cloudflare,
body.layout-heroku,
body.layout-mailgun,
body.layout-digitalocean,
body.layout-aws,
body.layout-docs,
@ -17,7 +18,7 @@ body.layout-inner,
body.layout-downloads,
body.layout-intro{
background: @light-black url('../images/sidebar-wire.png') left 62px no-repeat;
>.container{
.col-md-8[role=main]{
min-height: 800px;

View File

@ -1618,6 +1618,7 @@ body.layout-consul,
body.layout-dnsimple,
body.layout-cloudflare,
body.layout-heroku,
body.layout-mailgun,
body.layout-digitalocean,
body.layout-aws,
body.layout-docs,
@ -1630,6 +1631,7 @@ body.layout-consul > .container .col-md-8[role=main],
body.layout-dnsimple > .container .col-md-8[role=main],
body.layout-cloudflare > .container .col-md-8[role=main],
body.layout-heroku > .container .col-md-8[role=main],
body.layout-mailgun > .container .col-md-8[role=main],
body.layout-digitalocean > .container .col-md-8[role=main],
body.layout-aws > .container .col-md-8[role=main],
body.layout-docs > .container .col-md-8[role=main],
@ -1643,6 +1645,7 @@ body.layout-consul > .container .col-md-8[role=main] > div,
body.layout-dnsimple > .container .col-md-8[role=main] > div,
body.layout-cloudflare > .container .col-md-8[role=main] > div,
body.layout-heroku > .container .col-md-8[role=main] > div,
body.layout-mailgun > .container .col-md-8[role=main] > div,
body.layout-digitalocean > .container .col-md-8[role=main] > div,
body.layout-aws > .container .col-md-8[role=main] > div,
body.layout-docs > .container .col-md-8[role=main] > div,