diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index e469c6756943..1feb8ea9d615 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -226,6 +226,7 @@ func Provider() terraform.ResourceProvider { "aws_route_table_association": resourceAwsRouteTableAssociation(), "aws_s3_bucket": resourceAwsS3Bucket(), "aws_s3_bucket_object": resourceAwsS3BucketObject(), + "aws_s3_bucket_notification": resourceAwsS3BucketNotification(), "aws_security_group": resourceAwsSecurityGroup(), "aws_security_group_rule": resourceAwsSecurityGroupRule(), "aws_spot_instance_request": resourceAwsSpotInstanceRequest(), diff --git a/builtin/providers/aws/resource_aws_s3_bucket_notification.go b/builtin/providers/aws/resource_aws_s3_bucket_notification.go new file mode 100644 index 000000000000..562dc723aa76 --- /dev/null +++ b/builtin/providers/aws/resource_aws_s3_bucket_notification.go @@ -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 +} diff --git a/builtin/providers/aws/resource_aws_s3_bucket_notification_test.go b/builtin/providers/aws/resource_aws_s3_bucket_notification_test.go new file mode 100644 index 000000000000..58ffd9a1d0b6 --- /dev/null +++ b/builtin/providers/aws/resource_aws_s3_bucket_notification_test.go @@ -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 = <aws_s3_bucket_object + > + aws_s3_bucket_notification + +