provider/aws: Support S3 bucket notification

* Implement aws_s3_bucket_notification resource
This commit is contained in:
KOJIMA Kazunori 2016-04-05 11:23:06 +09:00 committed by Paul Stack
parent b04b508b54
commit d646682d7a
5 changed files with 1158 additions and 0 deletions

View File

@ -228,6 +228,7 @@ func Provider() terraform.ResourceProvider {
"aws_route_table_association": resourceAwsRouteTableAssociation(), "aws_route_table_association": resourceAwsRouteTableAssociation(),
"aws_s3_bucket": resourceAwsS3Bucket(), "aws_s3_bucket": resourceAwsS3Bucket(),
"aws_s3_bucket_object": resourceAwsS3BucketObject(), "aws_s3_bucket_object": resourceAwsS3BucketObject(),
"aws_s3_bucket_notification": resourceAwsS3BucketNotification(),
"aws_security_group": resourceAwsSecurityGroup(), "aws_security_group": resourceAwsSecurityGroup(),
"aws_security_group_rule": resourceAwsSecurityGroupRule(), "aws_security_group_rule": resourceAwsSecurityGroupRule(),
"aws_spot_instance_request": resourceAwsSpotInstanceRequest(), "aws_spot_instance_request": resourceAwsSpotInstanceRequest(),

View File

@ -0,0 +1,464 @@
package aws
import (
"fmt"
"log"
"strings"
"time"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
)
func resourceAwsS3BucketNotification() *schema.Resource {
return &schema.Resource{
Create: resourceAwsS3BucketNotificationPut,
Read: resourceAwsS3BucketNotificationRead,
Update: resourceAwsS3BucketNotificationPut,
Delete: resourceAwsS3BucketNotificationDelete,
Schema: map[string]*schema.Schema{
"bucket": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"topic": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"filter_prefix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"filter_suffix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"topic_arn": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"events": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
},
},
"queue": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"filter_prefix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"filter_suffix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"queue_arn": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"events": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
},
},
"lambda_function": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"filter_prefix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"filter_suffix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"lambda_function_arn": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"events": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
},
},
},
},
}
}
func resourceAwsS3BucketNotificationPut(d *schema.ResourceData, meta interface{}) error {
s3conn := meta.(*AWSClient).s3conn
bucket := d.Get("bucket").(string)
// TopicNotifications
topicNotifications := d.Get("topic").([]interface{})
topicConfigs := make([]*s3.TopicConfiguration, 0, len(topicNotifications))
for i, c := range topicNotifications {
tc := &s3.TopicConfiguration{}
c := c.(map[string]interface{})
// Id
if val, ok := c["id"].(string); ok && val != "" {
tc.Id = aws.String(val)
} else {
tc.Id = aws.String(resource.PrefixedUniqueId("tf-s3-topic-"))
}
// TopicArn
if val, ok := c["topic_arn"].(string); ok {
tc.TopicArn = aws.String(val)
}
// Events
events := d.Get(fmt.Sprintf("topic.%d.events", i)).(*schema.Set).List()
tc.Events = make([]*string, 0, len(events))
for _, e := range events {
tc.Events = append(tc.Events, aws.String(e.(string)))
}
// Filter
filterRules := make([]*s3.FilterRule, 0, 2)
if val, ok := c["filter_prefix"].(string); ok && val != "" {
filterRule := &s3.FilterRule{
Name: aws.String("prefix"),
Value: aws.String(val),
}
filterRules = append(filterRules, filterRule)
}
if val, ok := c["filter_suffix"].(string); ok && val != "" {
filterRule := &s3.FilterRule{
Name: aws.String("suffix"),
Value: aws.String(val),
}
filterRules = append(filterRules, filterRule)
}
if len(filterRules) > 0 {
tc.Filter = &s3.NotificationConfigurationFilter{
Key: &s3.KeyFilter{
FilterRules: filterRules,
},
}
}
topicConfigs = append(topicConfigs, tc)
}
// SQS
queueNotifications := d.Get("queue").([]interface{})
queueConfigs := make([]*s3.QueueConfiguration, 0, len(queueNotifications))
for i, c := range queueNotifications {
qc := &s3.QueueConfiguration{}
c := c.(map[string]interface{})
// Id
if val, ok := c["id"].(string); ok && val != "" {
qc.Id = aws.String(val)
} else {
qc.Id = aws.String(resource.PrefixedUniqueId("tf-s3-queue-"))
}
// QueueArn
if val, ok := c["queue_arn"].(string); ok {
qc.QueueArn = aws.String(val)
}
// Events
events := d.Get(fmt.Sprintf("queue.%d.events", i)).(*schema.Set).List()
qc.Events = make([]*string, 0, len(events))
for _, e := range events {
qc.Events = append(qc.Events, aws.String(e.(string)))
}
// Filter
filterRules := make([]*s3.FilterRule, 0, 2)
if val, ok := c["filter_prefix"].(string); ok && val != "" {
filterRule := &s3.FilterRule{
Name: aws.String("prefix"),
Value: aws.String(val),
}
filterRules = append(filterRules, filterRule)
}
if val, ok := c["filter_suffix"].(string); ok && val != "" {
filterRule := &s3.FilterRule{
Name: aws.String("suffix"),
Value: aws.String(val),
}
filterRules = append(filterRules, filterRule)
}
if len(filterRules) > 0 {
qc.Filter = &s3.NotificationConfigurationFilter{
Key: &s3.KeyFilter{
FilterRules: filterRules,
},
}
}
queueConfigs = append(queueConfigs, qc)
}
// Lambda
lambdaFunctionNotifications := d.Get("lambda_function").([]interface{})
lambdaConfigs := make([]*s3.LambdaFunctionConfiguration, 0, len(lambdaFunctionNotifications))
for i, c := range lambdaFunctionNotifications {
lc := &s3.LambdaFunctionConfiguration{}
c := c.(map[string]interface{})
// Id
if val, ok := c["id"].(string); ok && val != "" {
lc.Id = aws.String(val)
} else {
lc.Id = aws.String(resource.PrefixedUniqueId("tf-s3-lambda-"))
}
// LambdaFunctionArn
if val, ok := c["lambda_function_arn"].(string); ok {
lc.LambdaFunctionArn = aws.String(val)
}
// Events
events := d.Get(fmt.Sprintf("lambda_function.%d.events", i)).(*schema.Set).List()
lc.Events = make([]*string, 0, len(events))
for _, e := range events {
lc.Events = append(lc.Events, aws.String(e.(string)))
}
// Filter
filterRules := make([]*s3.FilterRule, 0, 2)
if val, ok := c["filter_prefix"].(string); ok && val != "" {
filterRule := &s3.FilterRule{
Name: aws.String("prefix"),
Value: aws.String(val),
}
filterRules = append(filterRules, filterRule)
}
if val, ok := c["filter_suffix"].(string); ok && val != "" {
filterRule := &s3.FilterRule{
Name: aws.String("suffix"),
Value: aws.String(val),
}
filterRules = append(filterRules, filterRule)
}
if len(filterRules) > 0 {
lc.Filter = &s3.NotificationConfigurationFilter{
Key: &s3.KeyFilter{
FilterRules: filterRules,
},
}
}
lambdaConfigs = append(lambdaConfigs, lc)
}
notificationConfiguration := &s3.NotificationConfiguration{}
if len(lambdaConfigs) > 0 {
notificationConfiguration.LambdaFunctionConfigurations = lambdaConfigs
}
if len(queueConfigs) > 0 {
notificationConfiguration.QueueConfigurations = queueConfigs
}
if len(topicConfigs) > 0 {
notificationConfiguration.TopicConfigurations = topicConfigs
}
i := &s3.PutBucketNotificationConfigurationInput{
Bucket: aws.String(bucket),
NotificationConfiguration: notificationConfiguration,
}
log.Printf("[DEBUG] S3 bucket: %s, Putting notification: %v", bucket, i)
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
if _, err := s3conn.PutBucketNotificationConfiguration(i); err != nil {
if awserr, ok := err.(awserr.Error); ok {
switch awserr.Message() {
case "Unable to validate the following destination configurations":
return resource.RetryableError(awserr)
}
}
// Didn't recognize the error, so shouldn't retry.
return resource.NonRetryableError(err)
}
// Successful put configuration
return nil
})
if err != nil {
return fmt.Errorf("Error putting S3 notification configuration: %s", err)
}
d.SetId(bucket)
return resourceAwsS3BucketNotificationRead(d, meta)
}
func resourceAwsS3BucketNotificationDelete(d *schema.ResourceData, meta interface{}) error {
s3conn := meta.(*AWSClient).s3conn
i := &s3.PutBucketNotificationConfigurationInput{
Bucket: aws.String(d.Id()),
NotificationConfiguration: &s3.NotificationConfiguration{},
}
log.Printf("[DEBUG] S3 bucket: %s, Deleting notification: %v", d.Id(), i)
_, err := s3conn.PutBucketNotificationConfiguration(i)
if err != nil {
return fmt.Errorf("Error deleting S3 notification configuration: %s", err)
}
d.SetId("")
return nil
}
func resourceAwsS3BucketNotificationRead(d *schema.ResourceData, meta interface{}) error {
s3conn := meta.(*AWSClient).s3conn
var err error
_, err = s3conn.HeadBucket(&s3.HeadBucketInput{
Bucket: aws.String(d.Id()),
})
if err != nil {
if awsError, ok := err.(awserr.RequestFailure); ok && awsError.StatusCode() == 404 {
log.Printf("[WARN] S3 Bucket (%s) not found, error code (404)", d.Id())
d.SetId("")
return nil
} else {
// some of the AWS SDK's errors can be empty strings, so let's add
// some additional context.
return fmt.Errorf("error reading S3 bucket \"%s\": %s", d.Id(), err)
}
}
// Read the notification configuration
notificationConfigs, err := s3conn.GetBucketNotificationConfiguration(&s3.GetBucketNotificationConfigurationRequest{
Bucket: aws.String(d.Id()),
})
if err != nil {
return err
}
log.Printf("[DEBUG] S3 Bucket: %s, get notification: %v", d.Id(), notificationConfigs)
// Topic Notification
if err := d.Set("topic", flattenTopicConfigurations(notificationConfigs.TopicConfigurations)); err != nil {
return fmt.Errorf("error reading S3 bucket \"%s\" topic notification: %s", d.Id(), err)
}
// SQS Notification
if err := d.Set("queue", flattenQueueConfigurations(notificationConfigs.QueueConfigurations)); err != nil {
return fmt.Errorf("error reading S3 bucket \"%s\" queue notification: %s", d.Id(), err)
}
// Lambda Notification
if err := d.Set("lambda_function", flattenLambdaFunctionConfigurations(notificationConfigs.LambdaFunctionConfigurations)); err != nil {
return fmt.Errorf("error reading S3 bucket \"%s\" lambda function notification: %s", d.Id(), err)
}
return nil
}
func flattenNotificationConfigurationFilter(filter *s3.NotificationConfigurationFilter) map[string]interface{} {
filterRules := map[string]interface{}{}
for _, f := range filter.Key.FilterRules {
if strings.ToLower(*f.Name) == s3.FilterRuleNamePrefix {
filterRules["filter_prefix"] = *f.Value
}
if strings.ToLower(*f.Name) == s3.FilterRuleNameSuffix {
filterRules["filter_suffix"] = *f.Value
}
}
return filterRules
}
func flattenTopicConfigurations(configs []*s3.TopicConfiguration) []map[string]interface{} {
topicNotifications := make([]map[string]interface{}, 0, len(configs))
for _, notification := range configs {
var conf map[string]interface{}
if filter := notification.Filter; filter != nil {
conf = flattenNotificationConfigurationFilter(filter)
} else {
conf = map[string]interface{}{}
}
conf["id"] = *notification.Id
conf["events"] = schema.NewSet(schema.HashString, flattenStringList(notification.Events))
conf["topic_arn"] = *notification.TopicArn
topicNotifications = append(topicNotifications, conf)
}
return topicNotifications
}
func flattenQueueConfigurations(configs []*s3.QueueConfiguration) []map[string]interface{} {
queueNotifications := make([]map[string]interface{}, 0, len(configs))
for _, notification := range configs {
var conf map[string]interface{}
if filter := notification.Filter; filter != nil {
conf = flattenNotificationConfigurationFilter(filter)
} else {
conf = map[string]interface{}{}
}
conf["id"] = *notification.Id
conf["events"] = schema.NewSet(schema.HashString, flattenStringList(notification.Events))
conf["queue_arn"] = *notification.QueueArn
queueNotifications = append(queueNotifications, conf)
}
return queueNotifications
}
func flattenLambdaFunctionConfigurations(configs []*s3.LambdaFunctionConfiguration) []map[string]interface{} {
lambdaFunctionNotifications := make([]map[string]interface{}, 0, len(configs))
for _, notification := range configs {
var conf map[string]interface{}
if filter := notification.Filter; filter != nil {
conf = flattenNotificationConfigurationFilter(filter)
} else {
conf = map[string]interface{}{}
}
conf["id"] = *notification.Id
conf["events"] = schema.NewSet(schema.HashString, flattenStringList(notification.Events))
conf["lambda_function_arn"] = *notification.LambdaFunctionArn
lambdaFunctionNotifications = append(lambdaFunctionNotifications, conf)
}
return lambdaFunctionNotifications
}

View File

@ -0,0 +1,519 @@
package aws
import (
"fmt"
"reflect"
"sort"
"testing"
"time"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/s3"
)
func TestAccAWSS3Bucket_Notification(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketNotificationDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSS3BucketConfigWithTopicNotification(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketTopicNotification(
"aws_s3_bucket.bucket",
"notification-sns1",
"aws_sns_topic.topic",
[]string{"s3:ObjectCreated:*", "s3:ObjectRemoved:Delete"},
&s3.KeyFilter{
FilterRules: []*s3.FilterRule{
&s3.FilterRule{
Name: aws.String("Prefix"),
Value: aws.String(fmt.Sprintf("%d/", rInt)),
},
&s3.FilterRule{
Name: aws.String("Suffix"),
Value: aws.String(".txt"),
},
},
},
),
testAccCheckAWSS3BucketTopicNotification(
"aws_s3_bucket.bucket",
"notification-sns2",
"aws_sns_topic.topic",
[]string{"s3:ObjectCreated:*", "s3:ObjectRemoved:Delete"},
&s3.KeyFilter{
FilterRules: []*s3.FilterRule{
&s3.FilterRule{
Name: aws.String("Suffix"),
Value: aws.String(".log"),
},
},
},
),
),
},
resource.TestStep{
Config: testAccAWSS3BucketConfigWithQueueNotification(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketQueueNotification(
"aws_s3_bucket.bucket",
"notification-sqs",
"aws_sqs_queue.queue",
[]string{"s3:ObjectCreated:*", "s3:ObjectRemoved:Delete"},
&s3.KeyFilter{
FilterRules: []*s3.FilterRule{
&s3.FilterRule{
Name: aws.String("Prefix"),
Value: aws.String(fmt.Sprintf("%d/", rInt)),
},
&s3.FilterRule{
Name: aws.String("Suffix"),
Value: aws.String(".mp4"),
},
},
},
),
),
},
resource.TestStep{
Config: testAccAWSS3BucketConfigWithLambdaNotification(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketLambdaFunctionConfiguration(
"aws_s3_bucket.bucket",
"notification-lambda",
"aws_lambda_function.func",
[]string{"s3:ObjectCreated:*", "s3:ObjectRemoved:Delete"},
&s3.KeyFilter{
FilterRules: []*s3.FilterRule{
&s3.FilterRule{
Name: aws.String("Prefix"),
Value: aws.String(fmt.Sprintf("%d/", rInt)),
},
&s3.FilterRule{
Name: aws.String("Suffix"),
Value: aws.String(".png"),
},
},
},
),
),
},
},
})
}
func TestAccAWSS3Bucket_NotificationWithoutFilter(t *testing.T) {
rInt := acctest.RandInt()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3BucketNotificationDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSS3BucketConfigWithTopicNotificationWithoutFilter(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3BucketTopicNotification(
"aws_s3_bucket.bucket",
"notification-sns1",
"aws_sns_topic.topic",
[]string{"s3:ObjectCreated:*", "s3:ObjectRemoved:Delete"},
nil,
),
),
},
},
})
}
func testAccCheckAWSS3BucketNotificationDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).s3conn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_s3_bucket_notification" {
continue
}
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
out, err := conn.GetBucketNotificationConfiguration(&s3.GetBucketNotificationConfigurationRequest{
Bucket: aws.String(rs.Primary.ID),
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoSuchBucket" {
return nil
}
return resource.NonRetryableError(err)
}
if len(out.TopicConfigurations) > 0 {
return resource.RetryableError(fmt.Errorf("TopicConfigurations is exists: %v", out))
}
if len(out.LambdaFunctionConfigurations) > 0 {
return resource.RetryableError(fmt.Errorf("LambdaFunctionConfigurations is exists: %v", out))
}
if len(out.QueueConfigurations) > 0 {
return resource.RetryableError(fmt.Errorf("QueueConfigurations is exists: %v", out))
}
return nil
})
if err != nil {
return err
}
}
return nil
}
func testAccCheckAWSS3BucketTopicNotification(n, i, t string, events []string, filters *s3.KeyFilter) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, _ := s.RootModule().Resources[n]
topicArn := s.RootModule().Resources[t].Primary.ID
conn := testAccProvider.Meta().(*AWSClient).s3conn
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
out, err := conn.GetBucketNotificationConfiguration(&s3.GetBucketNotificationConfigurationRequest{
Bucket: aws.String(rs.Primary.ID),
})
if err != nil {
return resource.NonRetryableError(fmt.Errorf("GetBucketNotification error: %v", err))
}
eventSlice := sort.StringSlice(events)
eventSlice.Sort()
outputTopics := out.TopicConfigurations
matched := false
for _, outputTopic := range outputTopics {
if *outputTopic.Id == i {
matched = true
if *outputTopic.TopicArn != topicArn {
return resource.RetryableError(fmt.Errorf("bad topic arn, expected: %s, got %#v", topicArn, *outputTopic.TopicArn))
}
if filters != nil {
if !reflect.DeepEqual(filters, outputTopic.Filter.Key) {
return resource.RetryableError(fmt.Errorf("bad notification filters, expected: %#v, got %#v", filters, outputTopic.Filter.Key))
}
} else {
if outputTopic.Filter != nil {
return resource.RetryableError(fmt.Errorf("bad notification filters, expected: nil, got %#v", outputTopic.Filter))
}
}
outputEventSlice := sort.StringSlice(aws.StringValueSlice(outputTopic.Events))
outputEventSlice.Sort()
if !reflect.DeepEqual(eventSlice, outputEventSlice) {
return resource.RetryableError(fmt.Errorf("bad notification events, expected: %#v, got %#v", events, outputEventSlice))
}
}
}
if !matched {
return resource.RetryableError(fmt.Errorf("No match topic configurations: %#v", out))
}
return nil
})
return err
}
}
func testAccCheckAWSS3BucketQueueNotification(n, i, t string, events []string, filters *s3.KeyFilter) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, _ := s.RootModule().Resources[n]
queueArn := s.RootModule().Resources[t].Primary.Attributes["arn"]
conn := testAccProvider.Meta().(*AWSClient).s3conn
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
out, err := conn.GetBucketNotificationConfiguration(&s3.GetBucketNotificationConfigurationRequest{
Bucket: aws.String(rs.Primary.ID),
})
if err != nil {
return resource.NonRetryableError(fmt.Errorf("GetBucketNotification error: %v", err))
}
eventSlice := sort.StringSlice(events)
eventSlice.Sort()
outputQueues := out.QueueConfigurations
matched := false
for _, outputQueue := range outputQueues {
if *outputQueue.Id == i {
matched = true
if *outputQueue.QueueArn != queueArn {
return resource.RetryableError(fmt.Errorf("bad queue arn, expected: %s, got %#v", queueArn, *outputQueue.QueueArn))
}
if filters != nil {
if !reflect.DeepEqual(filters, outputQueue.Filter.Key) {
return resource.RetryableError(fmt.Errorf("bad notification filters, expected: %#v, got %#v", filters, outputQueue.Filter.Key))
}
} else {
if outputQueue.Filter != nil {
return resource.RetryableError(fmt.Errorf("bad notification filters, expected: nil, got %#v", outputQueue.Filter))
}
}
outputEventSlice := sort.StringSlice(aws.StringValueSlice(outputQueue.Events))
outputEventSlice.Sort()
if !reflect.DeepEqual(eventSlice, outputEventSlice) {
return resource.RetryableError(fmt.Errorf("bad notification events, expected: %#v, got %#v", events, outputEventSlice))
}
}
}
if !matched {
return resource.RetryableError(fmt.Errorf("No match queue configurations: %#v", out))
}
return nil
})
return err
}
}
func testAccCheckAWSS3BucketLambdaFunctionConfiguration(n, i, t string, events []string, filters *s3.KeyFilter) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, _ := s.RootModule().Resources[n]
funcArn := s.RootModule().Resources[t].Primary.Attributes["arn"]
conn := testAccProvider.Meta().(*AWSClient).s3conn
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
out, err := conn.GetBucketNotificationConfiguration(&s3.GetBucketNotificationConfigurationRequest{
Bucket: aws.String(rs.Primary.ID),
})
if err != nil {
return resource.NonRetryableError(fmt.Errorf("GetBucketNotification error: %v", err))
}
eventSlice := sort.StringSlice(events)
eventSlice.Sort()
outputFunctions := out.LambdaFunctionConfigurations
matched := false
for _, outputFunc := range outputFunctions {
if *outputFunc.Id == i {
matched = true
if *outputFunc.LambdaFunctionArn != funcArn {
return resource.RetryableError(fmt.Errorf("bad lambda function arn, expected: %s, got %#v", funcArn, *outputFunc.LambdaFunctionArn))
}
if filters != nil {
if !reflect.DeepEqual(filters, outputFunc.Filter.Key) {
return resource.RetryableError(fmt.Errorf("bad notification filters, expected: %#v, got %#v", filters, outputFunc.Filter.Key))
}
} else {
if outputFunc.Filter != nil {
return resource.RetryableError(fmt.Errorf("bad notification filters, expected: nil, got %#v", outputFunc.Filter))
}
}
outputEventSlice := sort.StringSlice(aws.StringValueSlice(outputFunc.Events))
outputEventSlice.Sort()
if !reflect.DeepEqual(eventSlice, outputEventSlice) {
return resource.RetryableError(fmt.Errorf("bad notification events, expected: %#v, got %#v", events, outputEventSlice))
}
}
}
if !matched {
return resource.RetryableError(fmt.Errorf("No match lambda function configurations: %#v", out))
}
return nil
})
return err
}
}
func testAccAWSS3BucketConfigWithTopicNotification(randInt int) string {
return fmt.Sprintf(`
resource "aws_sns_topic" "topic" {
name = "terraform-test-topic"
policy = <<POLICY
{
"Version":"2012-10-17",
"Statement":[{
"Sid": "",
"Effect": "Allow",
"Principal": {"AWS":"*"},
"Action": "SNS:Publish",
"Resource": "arn:aws:sns:*:*:terraform-test-topic",
"Condition":{
"ArnLike":{"aws:SourceArn":"${aws_s3_bucket.bucket.arn}"}
}
}]
}
POLICY
}
resource "aws_s3_bucket" "bucket" {
bucket = "tf-test-bucket-%d"
acl = "public-read"
}
resource "aws_s3_bucket_notification" "notification" {
bucket = "${aws_s3_bucket.bucket.id}"
topic {
id = "notification-sns1"
topic_arn = "${aws_sns_topic.topic.arn}"
events = [
"s3:ObjectCreated:*",
"s3:ObjectRemoved:Delete",
]
filter_prefix = "%d/"
filter_suffix = ".txt"
}
topic {
id = "notification-sns2"
topic_arn = "${aws_sns_topic.topic.arn}"
events = [
"s3:ObjectCreated:*",
"s3:ObjectRemoved:Delete",
]
filter_suffix = ".log"
}
}
`, randInt, randInt)
}
func testAccAWSS3BucketConfigWithQueueNotification(randInt int) string {
return fmt.Sprintf(`
resource "aws_sqs_queue" "queue" {
name = "terraform-test-queue-%d"
policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"sqs:SendMessage\",\"Resource\":\"arn:aws:sqs:*:*:terraform-test-queue-%d\",\"Condition\":{\"ArnEquals\":{\"aws:SourceArn\":\"${aws_s3_bucket.bucket.arn}\"}}}]}"
}
resource "aws_s3_bucket" "bucket" {
bucket = "tf-test-bucket-%d"
acl = "public-read"
}
resource "aws_s3_bucket_notification" "notification" {
bucket = "${aws_s3_bucket.bucket.id}"
queue {
id = "notification-sqs"
queue_arn = "${aws_sqs_queue.queue.arn}"
events = [
"s3:ObjectCreated:*",
"s3:ObjectRemoved:Delete",
]
filter_prefix = "%d/"
filter_suffix = ".mp4"
}
}
`, randInt, randInt, randInt, randInt)
}
func testAccAWSS3BucketConfigWithLambdaNotification(randInt int) string {
return fmt.Sprintf(`
resource "aws_iam_role" "iam_for_lambda" {
name = "iam_for_lambda"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_lambda_permission" "allow_bucket" {
statement_id = "AllowExecutionFromS3Bucket"
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.func.arn}"
principal = "s3.amazonaws.com"
source_arn = "${aws_s3_bucket.bucket.arn}"
}
resource "aws_lambda_function" "func" {
filename = "test-fixtures/lambdatest.zip"
function_name = "example_lambda_name"
role = "${aws_iam_role.iam_for_lambda.arn}"
handler = "exports.example"
}
resource "aws_s3_bucket" "bucket" {
bucket = "tf-test-bucket-%d"
acl = "public-read"
}
resource "aws_s3_bucket_notification" "notification" {
bucket = "${aws_s3_bucket.bucket.id}"
lambda_function {
id = "notification-lambda"
lambda_function_arn = "${aws_lambda_function.func.arn}"
events = [
"s3:ObjectCreated:*",
"s3:ObjectRemoved:Delete",
]
filter_prefix = "%d/"
filter_suffix = ".png"
}
}
`, randInt, randInt)
}
func testAccAWSS3BucketConfigWithTopicNotificationWithoutFilter(randInt int) string {
return fmt.Sprintf(`
resource "aws_sns_topic" "topic" {
name = "terraform-test-topic"
policy = <<POLICY
{
"Version":"2012-10-17",
"Statement":[{
"Sid": "",
"Effect": "Allow",
"Principal": {"AWS":"*"},
"Action": "SNS:Publish",
"Resource": "arn:aws:sns:*:*:terraform-test-topic",
"Condition":{
"ArnLike":{"aws:SourceArn":"${aws_s3_bucket.bucket.arn}"}
}
}]
}
POLICY
}
resource "aws_s3_bucket" "bucket" {
bucket = "tf-test-bucket-%d"
acl = "public-read"
}
resource "aws_s3_bucket_notification" "notification" {
bucket = "${aws_s3_bucket.bucket.id}"
topic {
id = "notification-sns1"
topic_arn = "${aws_sns_topic.topic.arn}"
events = [
"s3:ObjectCreated:*",
"s3:ObjectRemoved:Delete",
]
}
}
`, randInt)
}

View File

@ -0,0 +1,170 @@
---
layout: "aws"
page_title: "AWS: aws_s3_bucket_notification"
side_bar_current: "docs-aws-resource-s3-bucket-notification"
description: |-
Provides a S3 bucket notification resource.
---
# aws\_s3\_bucket\_notification
Provides a S3 bucket notification resource.
## Example Usage
### Add notification configuration to SNS Topic
```
resource "aws_sns_topic" "topic" {
name = "s3-event-notification-topic"
policy = <<POLICY
{
"Version":"2012-10-17",
"Statement":[{
"Effect": "Allow",
"Principal": {"AWS":"*"},
"Action": "SNS:Publish",
"Resource": "arn:aws:sns:*:*:s3-event-notification-topic",
"Condition":{
"ArnLike":{"aws:SourceArn":"${aws_s3_bucket.bucket.arn}"}
}
}]
}
POLICY
}
resource "aws_s3_bucket" "bucket" {
bucket = "your_bucket_name"
}
resource "aws_s3_bucket_notification" "bucket_notification" {
bucket = "${aws_s3_bucket.bucket.id}"
topic {
topic_arn = "${aws_sns_topic.topic.arn}"
events = ["s3:ObjectCreated:*"]
filter_suffix = ".log"
}
}
```
### Add notification configuration to SQS Queue
```
resource "aws_sqs_queue" "queue" {
name = "s3-event-notification-queue"
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "sqs:SendMessage",
"Resource": "arn:aws:sqs:*:*:s3-event-notification-queue",
"Condition": {
"ArnEquals": { "aws:SourceArn": "${aws_s3_bucket.bucket.arn}" }
}
}
]
}
POLICY
}
resource "aws_s3_bucket" "bucket" {
bucket = "your_bucket_name"
}
resource "aws_s3_bucket_notification" "bucket_notification" {
bucket = "${aws_s3_bucket.bucket.id}"
queue {
queue_arn = "${aws_sqs_queue.queue.arn}"
events = ["s3:ObjectCreated:*"]
filter_suffix = ".log"
}
}
```
### Add notification configuration to Lambda Function
```
resource "aws_iam_role" "iam_for_lambda" {
name = "iam_for_lambda"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow"
}
]
}
EOF
}
resource "aws_lambda_permission" "allow_bucket" {
statement_id = "AllowExecutionFromS3Bucket"
action = "lambda:InvokeFunction"
function_name = "${aws_lambda_function.func.arn}"
principal = "s3.amazonaws.com"
source_arn = "${aws_s3_bucket.bucket.arn}"
}
resource "aws_lambda_function" "func" {
filename = "your-function.zip"
function_name = "example_lambda_name"
role = "${aws_iam_role.iam_for_lambda.arn}"
handler = "exports.example"
}
resource "aws_s3_bucket" "bucket" {
bucket = "your_bucket_name"
}
resource "aws_s3_bucket_notification" "bucket_notification" {
bucket = "${aws_s3_bucket.bucket.id}"
lambda_function {
lambda_function_arn = "${aws_lambda_function.func.arn}"
events = ["s3:ObjectCreated:*"]
filter_prefix = "AWSLogs/"
filter_suffix = ".log"
}
}
```
## Argument Reference
The following arguments are supported:
* `bucket` - (Required) The name of the bucket to put notification configuration.
* `topic` - (Optional) The notification configuration to SNS Topic (documented below).
* `queue` - (Optional) The notification configuration to SQS Queue (documented below).
* `lambda_function` - (Optional) The notification configuration to Lambda Function (documented below).
The `topic` notification configuration supports the following:
* `id` - (Optional) Specifies unique identifier for each of the notification configurations.
* `topic_arn` - (Required) Specifies Amazon SNS topic ARN.
* `events` - (Required) Specifies [event](http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-event-types-and-destinations) for which to send notifications.
* `filter_prefix` - (Optional) Specifies object key name prefix.
* `filter_suffix` - (Optional) Specifies object key name suffix.
The `queue` notification configuration supports the following:
* `id` - (Optional) Specifies unique identifier for each of the notification configurations.
* `queue_arn` - (Required) Specifies Amazon SQS queue ARN.
* `events` - (Required) Specifies [event](http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-event-types-and-destinations) for which to send notifications.
* `filter_prefix` - (Optional) Specifies object key name prefix.
* `filter_suffix` - (Optional) Specifies object key name suffix.
The `lambda_function` notification configuration supports the following:
* `id` - (Optional) Specifies unique identifier for each of the notification configurations.
* `lambda_function_arn` - (Required) Specifies Amazon Lambda function ARN.
* `events` - (Required) Specifies [event](http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-event-types-and-destinations) for which to send notifications.
* `filter_prefix` - (Optional) Specifies object key name prefix.
* `filter_suffix` - (Optional) Specifies object key name suffix.

View File

@ -596,6 +596,10 @@
<a href="/docs/providers/aws/r/s3_bucket_object.html">aws_s3_bucket_object</a> <a href="/docs/providers/aws/r/s3_bucket_object.html">aws_s3_bucket_object</a>
</li> </li>
<li<%= sidebar_current("docs-aws-resource-s3-bucket-notification") %>>
<a href="/docs/providers/aws/r/s3_bucket_notification.html">aws_s3_bucket_notification</a>
</li>
</ul> </ul>
</li> </li>