Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for SES MAIL FROM #2029

Merged
merged 2 commits into from
Feb 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,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 @@ -1325,6 +1325,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
```