provider/aws: Support for SES Configuration sets and Event Destinations (#10735)

* adding configuration set

* adding event destination

* adding test, all tests passing

* adding doccumentation
This commit is contained in:
Ivan Fuyivara 2016-12-16 05:46:39 -06:00 committed by Paul Stack
parent 3228bd2fcb
commit 4d4ec6c0d1
8 changed files with 708 additions and 0 deletions

View File

@ -340,6 +340,8 @@ func Provider() terraform.ResourceProvider {
"aws_ses_receipt_filter": resourceAwsSesReceiptFilter(),
"aws_ses_receipt_rule": resourceAwsSesReceiptRule(),
"aws_ses_receipt_rule_set": resourceAwsSesReceiptRuleSet(),
"aws_ses_configuration_set": resourceAwsSesConfigurationSet(),
"aws_ses_event_destination": resourceAwsSesEventDestination(),
"aws_s3_bucket": resourceAwsS3Bucket(),
"aws_s3_bucket_policy": resourceAwsS3BucketPolicy(),
"aws_s3_bucket_object": resourceAwsS3BucketObject(),

View File

@ -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 resourceAwsSesConfigurationSet() *schema.Resource {
return &schema.Resource{
Create: resourceAwsSesConfigurationSetCreate,
Read: resourceAwsSesConfigurationSetRead,
Delete: resourceAwsSesConfigurationSetDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}
func resourceAwsSesConfigurationSetCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).sesConn
configurationSetName := d.Get("name").(string)
createOpts := &ses.CreateConfigurationSetInput{
ConfigurationSet: &ses.ConfigurationSet{
Name: aws.String(configurationSetName),
},
}
_, err := conn.CreateConfigurationSet(createOpts)
if err != nil {
return fmt.Errorf("Error creating SES configuration set: %s", err)
}
d.SetId(configurationSetName)
return resourceAwsSesConfigurationSetRead(d, meta)
}
func resourceAwsSesConfigurationSetRead(d *schema.ResourceData, meta interface{}) error {
configurationSetExists, err := findConfigurationSet(d.Id(), nil, meta)
if !configurationSetExists {
log.Printf("[WARN] SES Configuration Set (%s) not found", d.Id())
d.SetId("")
return nil
}
if err != nil {
return err
}
d.Set("name", d.Id())
return nil
}
func resourceAwsSesConfigurationSetDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).sesConn
log.Printf("[DEBUG] SES Delete Configuration Rule Set: %s", d.Id())
_, err := conn.DeleteConfigurationSet(&ses.DeleteConfigurationSetInput{
ConfigurationSetName: aws.String(d.Id()),
})
if err != nil {
return err
}
return nil
}
func findConfigurationSet(name string, token *string, meta interface{}) (bool, error) {
conn := meta.(*AWSClient).sesConn
configurationSetExists := false
listOpts := &ses.ListConfigurationSetsInput{
NextToken: token,
}
response, err := conn.ListConfigurationSets(listOpts)
for _, element := range response.ConfigurationSets {
if *element.Name == name {
configurationSetExists = true
}
}
if err != nil && !configurationSetExists && response.NextToken != nil {
configurationSetExists, err = findConfigurationSet(name, response.NextToken, meta)
}
if err != nil {
return false, err
}
return configurationSetExists, nil
}

View File

@ -0,0 +1,97 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/service/ses"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSSESConfigurationSet_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckSESConfigurationSetDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSSESConfigurationSetConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsSESConfigurationSetExists("aws_ses_configuration_set.test"),
),
},
},
})
}
func testAccCheckSESConfigurationSetDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).sesConn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_ses_configuration_set" {
continue
}
response, err := conn.ListConfigurationSets(&ses.ListConfigurationSetsInput{})
if err != nil {
return err
}
found := false
for _, element := range response.ConfigurationSets {
if *element.Name == "some-configuration-set" {
found = true
}
}
if found {
return fmt.Errorf("The configuration set still exists")
}
}
return nil
}
func testAccCheckAwsSESConfigurationSetExists(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("SES configuration set not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("SES configuration set ID not set")
}
conn := testAccProvider.Meta().(*AWSClient).sesConn
response, err := conn.ListConfigurationSets(&ses.ListConfigurationSetsInput{})
if err != nil {
return err
}
found := false
for _, element := range response.ConfigurationSets {
if *element.Name == "some-configuration-set" {
found = true
}
}
if !found {
return fmt.Errorf("The configuration set was not created")
}
return nil
}
}
const testAccAWSSESConfigurationSetConfig = `
resource "aws_ses_configuration_set" "test" {
name = "some-configuration-set"
}
`

View File

@ -0,0 +1,214 @@
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 resourceAwsSesEventDestination() *schema.Resource {
return &schema.Resource{
Create: resourceAwsSesEventDestinationCreate,
Read: resourceAwsSesEventDestinationRead,
Delete: resourceAwsSesEventDestinationDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"configuration_set_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"enabled": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"matching_types": &schema.Schema{
Type: schema.TypeSet,
Required: true,
ForceNew: true,
Set: schema.HashString,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validateMatchingTypes,
},
},
"cloudwatch_destination": {
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"kinesis_destination"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"default_value": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"dimension_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"value_source": &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateDimensionValueSource,
},
},
},
},
"kinesis_destination": {
Type: schema.TypeSet,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"cloudwatch_destination"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"stream_arn": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"role_arn": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
},
},
}
}
func resourceAwsSesEventDestinationCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).sesConn
configurationSetName := d.Get("configuration_set_name").(string)
eventDestinationName := d.Get("name").(string)
enabled := d.Get("enabled").(bool)
matchingEventTypes := d.Get("matching_types").(*schema.Set).List()
createOpts := &ses.CreateConfigurationSetEventDestinationInput{
ConfigurationSetName: aws.String(configurationSetName),
EventDestination: &ses.EventDestination{
Name: aws.String(eventDestinationName),
Enabled: aws.Bool(enabled),
MatchingEventTypes: expandStringList(matchingEventTypes),
},
}
if v, ok := d.GetOk("cloudwatch_destination"); ok {
destination := v.(*schema.Set).List()
createOpts.EventDestination.CloudWatchDestination = &ses.CloudWatchDestination{
DimensionConfigurations: generateCloudWatchDestination(destination),
}
log.Printf("[DEBUG] Creating cloudwatch destination: %#v", destination)
}
if v, ok := d.GetOk("kinesis_destination"); ok {
destination := v.(*schema.Set).List()
if len(destination) > 1 {
return fmt.Errorf("You can only define a single kinesis destination per record")
}
kinesis := destination[0].(map[string]interface{})
createOpts.EventDestination.KinesisFirehoseDestination = &ses.KinesisFirehoseDestination{
DeliveryStreamARN: aws.String(kinesis["stream_arn"].(string)),
IAMRoleARN: aws.String(kinesis["role_arn"].(string)),
}
log.Printf("[DEBUG] Creating kinesis destination: %#v", kinesis)
}
_, err := conn.CreateConfigurationSetEventDestination(createOpts)
if err != nil {
return fmt.Errorf("Error creating SES configuration set event destination: %s", err)
}
d.SetId(eventDestinationName)
log.Printf("[WARN] SES DONE")
return resourceAwsSesEventDestinationRead(d, meta)
}
func resourceAwsSesEventDestinationRead(d *schema.ResourceData, meta interface{}) error {
return nil
}
func resourceAwsSesEventDestinationDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).sesConn
log.Printf("[DEBUG] SES Delete Configuration Set Destination: %s", d.Id())
_, err := conn.DeleteConfigurationSetEventDestination(&ses.DeleteConfigurationSetEventDestinationInput{
ConfigurationSetName: aws.String(d.Get("configuration_set_name").(string)),
EventDestinationName: aws.String(d.Id()),
})
if err != nil {
return err
}
return nil
}
func validateMatchingTypes(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
matchingTypes := map[string]bool{
"send": true,
"reject": true,
"bounce": true,
"complaint": true,
"delivery": true,
}
if !matchingTypes[value] {
errors = append(errors, fmt.Errorf("%q must be a valid matching event type value: %q", k, value))
}
return
}
func validateDimensionValueSource(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
matchingSource := map[string]bool{
"messageTag": true,
"emailHeader": true,
}
if !matchingSource[value] {
errors = append(errors, fmt.Errorf("%q must be a valid dimension value: %q", k, value))
}
return
}
func generateCloudWatchDestination(v []interface{}) []*ses.CloudWatchDimensionConfiguration {
b := make([]*ses.CloudWatchDimensionConfiguration, len(v))
for i, vI := range v {
cloudwatch := vI.(map[string]interface{})
b[i] = &ses.CloudWatchDimensionConfiguration{
DefaultDimensionValue: aws.String(cloudwatch["default_value"].(string)),
DimensionName: aws.String(cloudwatch["dimension_name"].(string)),
DimensionValueSource: aws.String(cloudwatch["value_source"].(string)),
}
}
return b
}

View File

@ -0,0 +1,185 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/service/ses"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSSESEventDestination_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
},
Providers: testAccProviders,
CheckDestroy: testAccCheckSESEventDestinationDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSSESEventDestinationConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsSESEventDestinationExists("aws_ses_configuration_set.test"),
resource.TestCheckResourceAttr(
"aws_ses_event_destination.kinesis", "name", "event-destination-kinesis"),
resource.TestCheckResourceAttr(
"aws_ses_event_destination.cloudwatch", "name", "event-destination-cloudwatch"),
),
},
},
})
}
func testAccCheckSESEventDestinationDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).sesConn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_ses_configuration_set" {
continue
}
response, err := conn.ListConfigurationSets(&ses.ListConfigurationSetsInput{})
if err != nil {
return err
}
found := false
for _, element := range response.ConfigurationSets {
if *element.Name == "some-configuration-set" {
found = true
}
}
if found {
return fmt.Errorf("The configuration set still exists")
}
}
return nil
}
func testAccCheckAwsSESEventDestinationExists(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("SES event destination not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("SES event destination ID not set")
}
conn := testAccProvider.Meta().(*AWSClient).sesConn
response, err := conn.ListConfigurationSets(&ses.ListConfigurationSetsInput{})
if err != nil {
return err
}
found := false
for _, element := range response.ConfigurationSets {
if *element.Name == "some-configuration-set" {
found = true
}
}
if !found {
return fmt.Errorf("The configuration set was not created")
}
return nil
}
}
const testAccAWSSESEventDestinationConfig = `
resource "aws_s3_bucket" "bucket" {
bucket = "tf-test-bucket-format"
acl = "private"
}
resource "aws_iam_role" "firehose_role" {
name = "firehose_test_role_test"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "firehose.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
},
{
"Effect": "Allow",
"Principal": {
"Service": "ses.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_kinesis_firehose_delivery_stream" "test_stream" {
name = "terraform-kinesis-firehose-test-stream-test"
destination = "s3"
s3_configuration {
role_arn = "${aws_iam_role.firehose_role.arn}"
bucket_arn = "${aws_s3_bucket.bucket.arn}"
}
}
resource "aws_iam_role_policy" "firehose_delivery_policy" {
name = "tf-delivery-policy-test"
role = "${aws_iam_role.firehose_role.id}"
policy = "${data.aws_iam_policy_document.fh_felivery_document.json}"
}
data "aws_iam_policy_document" "fh_felivery_document" {
statement {
sid = "GiveSESPermissionToPutFirehose"
actions = [
"firehose:PutRecord",
"firehose:PutRecordBatch",
]
resources = [
"*",
]
}
}
resource "aws_ses_configuration_set" "test" {
name = "some-configuration-set"
}
resource "aws_ses_event_destination" "kinesis" {
name = "event-destination-kinesis",
configuration_set_name = "${aws_ses_configuration_set.test.name}",
enabled = true,
matching_types = ["bounce", "send"],
kinesis_destination = {
stream_arn = "${aws_kinesis_firehose_delivery_stream.test_stream.arn}",
role_arn = "${aws_iam_role.firehose_role.arn}"
}
}
resource "aws_ses_event_destination" "cloudwatch" {
name = "event-destination-cloudwatch",
configuration_set_name = "${aws_ses_configuration_set.test.name}",
enabled = true,
matching_types = ["bounce", "send"],
cloudwatch_destination = {
default_value = "default"
dimension_name = "dimension"
value_source = "emailHeader"
}
}
`

View File

@ -0,0 +1,25 @@
---
layout: "aws"
page_title: "AWS: ses_configuration_set"
sidebar_current: "docs-aws-resource-ses-configuration_set"
description: |-
Provides an SES configuration set
---
# aws\_ses\_configuration_set
Provides an SES configuration set resource
## Example Usage
```
resource "aws_ses_configuration_set" "test" {
name = "some-configuration-set-test"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the configuration set

View File

@ -0,0 +1,67 @@
---
layout: "aws"
page_title: "AWS: ses_event_destination"
sidebar_current: "docs-aws-resource-ses-event-destination"
description: |-
Provides an SES event destination
---
# aws\_ses\_event_destination
Provides an SES event destination
## Example Usage
```
# Add a firehose event destination to a configuration set
resource "aws_ses_event_destination" "kinesis" {
name = "event-destination-kinesis",
configuration_set_name = "${aws_ses_configuration_set.test.name}",
enabled = true,
matching_types = ["bounce", "send"],
kinesis_destination = {
stream_arn = "${aws_kinesis_firehose_delivery_stream.test_stream.arn}",
role_arn = "${aws_iam_role.firehose_role.arn}"
}
}
# CloudWatch event destination
resource "aws_ses_event_destination" "cloudwatch" {
name = "event-destination-cloudwatch",
configuration_set_name = "${aws_ses_configuration_set.test.name}",
enabled = true,
matching_types = ["bounce", "send"],
cloudwatch_destination = {
default_value = "default"
dimension_name = "dimension"
value_source = "emailHeader"
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the event destination
* `configuration_set_name` - (Required) The name of the configuration set
* `enabled` - (Optional) If true, the event destination will be enabled
* `matching_types` - (Required) A list of matching types. May be any of `"send"`, `"reject"`, `"bounce"`, `"complaint"`, or `"delivery"`.
* `cloudwatch_destination` - (Optional) CloudWatch destination for the events
* `kinesis_destination` - (Optional) Send the events to a kinesis firehose destination
~> **NOTE:** You can specify `"cloudwatch_destination"` or `"kinesis_destination"` but not both
CloudWatch Destination requires the following:
* `default_value` - (Required) The default value for the event
* `dimension_name` - (Required) The name for the dimension
* `value_source` - (Required) The source for the value. It can be either `"messageTag"` or `"emailHeader"`
Kinesis Destination requires the following:
* `stream_arn` - (Required) The ARN of the Kinesis Stream
* `role_arn` - (Required) The ARN of the role that has permissions to access the Kinesis Stream

View File

@ -962,6 +962,14 @@
<a href="/docs/providers/aws/r/ses_receipt_rule_set.html">aws_ses_receipt_rule_set</a>
</li>
<li<%= sidebar_current("docs-aws-resource-ses-configuration-set") %>>
<a href="/docs/providers/aws/r/ses_configuration_set.html">aws_ses_configuration_set</a>
</li>
<li<%= sidebar_current("docs-aws-resource-ses-event-destination") %>>
<a href="/docs/providers/aws/r/ses_event_destination.html">aws_ses_event_destination</a>
</li>
</ul>
</li>