Skip to content

Commit

Permalink
Merge pull request #2029 from mintuhouse/f-ses-mail-from
Browse files Browse the repository at this point in the history
Add support for SES MAIL FROM
  • Loading branch information
bflad authored Feb 16, 2018
2 parents 3dadf23 + 428b4e6 commit 9ae94bb
Show file tree
Hide file tree
Showing 5 changed files with 355 additions and 0 deletions.
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ func Provider() terraform.ResourceProvider {
"aws_ses_active_receipt_rule_set": resourceAwsSesActiveReceiptRuleSet(),
"aws_ses_domain_identity": resourceAwsSesDomainIdentity(),
"aws_ses_domain_dkim": resourceAwsSesDomainDkim(),
"aws_ses_domain_mail_from": resourceAwsSesDomainMailFrom(),
"aws_ses_receipt_filter": resourceAwsSesReceiptFilter(),
"aws_ses_receipt_rule": resourceAwsSesReceiptRule(),
"aws_ses_receipt_rule_set": resourceAwsSesReceiptRuleSet(),
Expand Down
110 changes: 110 additions & 0 deletions aws/resource_aws_ses_domain_mail_from.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package aws

import (
"fmt"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ses"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsSesDomainMailFrom() *schema.Resource {
return &schema.Resource{
Create: resourceAwsSesDomainMailFromSet,
Read: resourceAwsSesDomainMailFromRead,
Update: resourceAwsSesDomainMailFromSet,
Delete: resourceAwsSesDomainMailFromDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"domain": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"mail_from_domain": {
Type: schema.TypeString,
Required: true,
},
"behavior_on_mx_failure": {
Type: schema.TypeString,
Optional: true,
Default: ses.BehaviorOnMXFailureUseDefaultValue,
},
},
}
}

func resourceAwsSesDomainMailFromSet(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).sesConn

behaviorOnMxFailure := d.Get("behavior_on_mx_failure").(string)
domainName := d.Get("domain").(string)
mailFromDomain := d.Get("mail_from_domain").(string)

input := &ses.SetIdentityMailFromDomainInput{
BehaviorOnMXFailure: aws.String(behaviorOnMxFailure),
Identity: aws.String(domainName),
MailFromDomain: aws.String(mailFromDomain),
}

_, err := conn.SetIdentityMailFromDomain(input)
if err != nil {
return fmt.Errorf("Error setting MAIL FROM domain: %s", err)
}

d.SetId(domainName)

return resourceAwsSesDomainMailFromRead(d, meta)
}

func resourceAwsSesDomainMailFromRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).sesConn

domainName := d.Id()

readOpts := &ses.GetIdentityMailFromDomainAttributesInput{
Identities: []*string{
aws.String(domainName),
},
}

out, err := conn.GetIdentityMailFromDomainAttributes(readOpts)
if err != nil {
log.Printf("error fetching MAIL FROM domain attributes for %s: %s", domainName, err)
return err
}

d.Set("domain", domainName)

if v, ok := out.MailFromDomainAttributes[domainName]; ok {
d.Set("behavior_on_mx_failure", v.BehaviorOnMXFailure)
d.Set("mail_from_domain", v.MailFromDomain)
} else {
d.Set("behavior_on_mx_failure", v.BehaviorOnMXFailure)
d.Set("mail_from_domain", "")
}

return nil
}

func resourceAwsSesDomainMailFromDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).sesConn

domainName := d.Id()

deleteOpts := &ses.SetIdentityMailFromDomainInput{
Identity: aws.String(domainName),
MailFromDomain: nil,
}

_, err := conn.SetIdentityMailFromDomain(deleteOpts)
if err != nil {
return fmt.Errorf("Error deleting SES domain identity: %s", err)
}

return nil
}
170 changes: 170 additions & 0 deletions aws/resource_aws_ses_domain_mail_from_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package aws

import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ses"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAwsSESDomainMailFrom_basic(t *testing.T) {
domain := fmt.Sprintf(
"%s.terraformtesting.com",
acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
mailFromDomain1 := fmt.Sprintf("bounce1.%s", domain)
mailFromDomain2 := fmt.Sprintf("bounce2.%s", domain)
resourceName := "aws_ses_domain_mail_from.test"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckSESDomainMailFromDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsSESDomainMailFromConfig(domain, mailFromDomain1),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsSESDomainMailFromExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "behavior_on_mx_failure", ses.BehaviorOnMXFailureUseDefaultValue),
resource.TestCheckResourceAttr(resourceName, "domain", domain),
resource.TestCheckResourceAttr(resourceName, "mail_from_domain", mailFromDomain1),
),
},
{
Config: testAccAwsSESDomainMailFromConfig(domain, mailFromDomain2),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsSESDomainMailFromExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "behavior_on_mx_failure", ses.BehaviorOnMXFailureUseDefaultValue),
resource.TestCheckResourceAttr(resourceName, "domain", domain),
resource.TestCheckResourceAttr(resourceName, "mail_from_domain", mailFromDomain2),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccAwsSESDomainMailFrom_behaviorOnMxFailure(t *testing.T) {
domain := fmt.Sprintf(
"%s.terraformtesting.com",
acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resourceName := "aws_ses_domain_mail_from.test"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckSESDomainMailFromDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsSESDomainMailFromConfig_behaviorOnMxFailure(domain, ses.BehaviorOnMXFailureUseDefaultValue),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsSESDomainMailFromExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "behavior_on_mx_failure", ses.BehaviorOnMXFailureUseDefaultValue),
),
},
{
Config: testAccAwsSESDomainMailFromConfig_behaviorOnMxFailure(domain, ses.BehaviorOnMXFailureRejectMessage),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsSESDomainMailFromExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "behavior_on_mx_failure", ses.BehaviorOnMXFailureRejectMessage),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckAwsSESDomainMailFromExists(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("SES Domain Identity not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("SES Domain Identity name not set")
}

domain := rs.Primary.ID
conn := testAccProvider.Meta().(*AWSClient).sesConn

params := &ses.GetIdentityMailFromDomainAttributesInput{
Identities: []*string{
aws.String(domain),
},
}

response, err := conn.GetIdentityMailFromDomainAttributes(params)
if err != nil {
return err
}

if response.MailFromDomainAttributes[domain] == nil {
return fmt.Errorf("SES Domain MAIL FROM %s not found in AWS", domain)
}

return nil
}
}

func testAccCheckSESDomainMailFromDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).sesConn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_ses_domain_mail_from" {
continue
}

input := &ses.GetIdentityMailFromDomainAttributesInput{
Identities: []*string{aws.String(rs.Primary.ID)},
}

out, err := conn.GetIdentityMailFromDomainAttributes(input)
if err != nil {
return fmt.Errorf("error fetching MAIL FROM domain attributes: %s", err)
}
if v, ok := out.MailFromDomainAttributes[rs.Primary.ID]; ok && v.MailFromDomain != nil && *v.MailFromDomain != "" {
return fmt.Errorf("MAIL FROM domain was not removed, found: %s", *v.MailFromDomain)
}
}

return nil
}

func testAccAwsSESDomainMailFromConfig(domain, mailFromDomain string) string {
return fmt.Sprintf(`
resource "aws_ses_domain_identity" "test" {
domain = "%s"
}
resource "aws_ses_domain_mail_from" "test" {
domain = "${aws_ses_domain_identity.test.domain}"
mail_from_domain = "%s"
}
`, domain, mailFromDomain)
}

func testAccAwsSESDomainMailFromConfig_behaviorOnMxFailure(domain, behaviorOnMxFailure string) string {
return fmt.Sprintf(`
resource "aws_ses_domain_identity" "test" {
domain = "%s"
}
resource "aws_ses_domain_mail_from" "test" {
behavior_on_mx_failure = "%s"
domain = "${aws_ses_domain_identity.test.domain}"
mail_from_domain = "bounce.${aws_ses_domain_identity.test.domain}"
}
`, domain, behaviorOnMxFailure)
}
4 changes: 4 additions & 0 deletions website/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,10 @@
<a href="/docs/providers/aws/r/ses_domain_dkim.html">aws_ses_domain_dkim</a>
</li>

<li<%= sidebar_current("docs-aws-resource-ses-domain-mail-from") %>>
<a href="/docs/providers/aws/r/ses_domain_mail_from.html">aws_ses_domain_mail_from</a>
</li>

<li<%= sidebar_current("docs-aws-resource-ses-receipt-filter") %>>
<a href="/docs/providers/aws/r/ses_receipt_filter.html">aws_ses_receipt_filter</a>
</li>
Expand Down
70 changes: 70 additions & 0 deletions website/docs/r/ses_domain_mail_from.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
layout: "aws"
page_title: "AWS: ses_domain_mail_from"
sidebar_current: "docs-aws-resource-ses-domain-mail-from"
description: |-
Provides an SES domain MAIL FROM resource
---

# aws_ses_domain_mail_from

Provides an SES domain MAIL FROM resource.

~> **NOTE:** For the MAIL FROM domain to be fully usable, this resource should be paired with the [aws_ses_domain_identity resource](/docs/providers/aws/r/ses_domain_identity.html). To validate the MAIL FROM domain, a DNS MX record is required. To pass SPF checks, a DNS TXT record may also be required. See the [Amazon SES MAIL FROM documentation](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/mail-from-set.html) for more information.

## Example Usage

```hcl
resource "aws_ses_domain_mail_from" "example" {
domain = "${aws_ses_domain_identity.example.domain}"
mail_from_domain = "bounce.${aws_ses_domain_identity.example.domain}"
}
# Example SES Domain Identity
resource "aws_ses_domain_identity" "example" {
domain = "example.com"
}
# Example Route53 MX record
resource "aws_route53_record" "example_ses_domain_mail_from_mx" {
zone_id = "${aws_route53_zone.example.id}"
name = "${aws_ses_domain_mail_from.example.mail_from_domain}"
type = "MX"
ttl = "600"
records = ["10 feedback-smtp.us-east-1.amazonses.com"] # Change to the region in which `aws_ses_domain_identity.example` is created
}
# Example Route53 TXT record for SPF
resource "aws_route53_record" "example_ses_domain_mail_from_txt" {
zone_id = "${aws_route53_zone.example.id}"
name = "${aws_ses_domain_mail_from.example.mail_from_domain}"
type = "TXT"
ttl = "600"
records = ["v=spf1 include:amazonses.com -all"]
}
```

## Argument Reference

The following arguments are required:

* `domain` - (Required) Verified domain name to generate DKIM tokens for.
* `mail_from_domain` - (Required) Subdomain (of above domain) which is to be used as MAIL FROM address (Required for DMARC validation)

The following arguments are optional:

* `behavior_on_mx_failure` - (Optional) The action that you want Amazon SES to take if it cannot successfully read the required MX record when you send an email. Defaults to `UseDefaultValue`. See the [SES API documentation](https://docs.aws.amazon.com/ses/latest/APIReference/API_SetIdentityMailFromDomain.html) for more information.

## Attributes Reference

In addition to the arguments, which are exported, the following attributes are exported:

* `id` - The domain name.

## Import

MAIL FROM domain can be imported using the `domain` attribute, e.g.

```
$ terraform import aws_ses_domain_mail_from.example example.com
```

0 comments on commit 9ae94bb

Please sign in to comment.