From 1d9468a5b89d0080c4b784bc12071e1dc353183d Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Fri, 26 Jan 2018 23:54:36 -0500 Subject: [PATCH 1/2] New Resource: aws_s3_bucket_metric --- aws/provider.go | 1 + aws/resource_aws_s3_bucket_metric.go | 217 ++++++ aws/resource_aws_s3_bucket_metric_test.go | 705 ++++++++++++++++++ website/aws.erb | 4 + website/docs/r/s3_bucket_metric.html.markdown | 69 ++ 5 files changed, 996 insertions(+) create mode 100644 aws/resource_aws_s3_bucket_metric.go create mode 100644 aws/resource_aws_s3_bucket_metric_test.go create mode 100644 website/docs/r/s3_bucket_metric.html.markdown diff --git a/aws/provider.go b/aws/provider.go index d5880d73079..3a4f259da14 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -422,6 +422,7 @@ func Provider() terraform.ResourceProvider { "aws_s3_bucket_policy": resourceAwsS3BucketPolicy(), "aws_s3_bucket_object": resourceAwsS3BucketObject(), "aws_s3_bucket_notification": resourceAwsS3BucketNotification(), + "aws_s3_bucket_metric": resourceAwsS3BucketMetric(), "aws_security_group": resourceAwsSecurityGroup(), "aws_default_security_group": resourceAwsDefaultSecurityGroup(), "aws_security_group_rule": resourceAwsSecurityGroupRule(), diff --git a/aws/resource_aws_s3_bucket_metric.go b/aws/resource_aws_s3_bucket_metric.go new file mode 100644 index 00000000000..639d82d6207 --- /dev/null +++ b/aws/resource_aws_s3_bucket_metric.go @@ -0,0 +1,217 @@ +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/service/s3" +) + +func resourceAwsS3BucketMetric() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsS3BucketMetricPut, + Read: resourceAwsS3BucketMetricRead, + Update: resourceAwsS3BucketMetricPut, + Delete: resourceAwsS3BucketMetricDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "bucket": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "filter": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prefix": { + Type: schema.TypeString, + Optional: true, + }, + "tags": tagsSchema(), + }, + }, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAwsS3BucketMetricPut(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).s3conn + bucket := d.Get("bucket").(string) + name := d.Get("name").(string) + + metricsConfiguration := &s3.MetricsConfiguration{ + Id: aws.String(name), + } + + if v, ok := d.GetOk("filter"); ok { + metricsConfiguration.Filter = expandS3MetricsFilter(v.([]interface{})[0].(map[string]interface{})) + } + + input := &s3.PutBucketMetricsConfigurationInput{ + Bucket: aws.String(bucket), + Id: aws.String(name), + MetricsConfiguration: metricsConfiguration, + } + + log.Printf("[DEBUG] Putting metric configuration: %s", input) + err := resource.Retry(1*time.Minute, func() *resource.RetryError { + _, err := conn.PutBucketMetricsConfiguration(input) + if err != nil { + if isAWSErr(err, s3.ErrCodeNoSuchBucket, "") { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + if err != nil { + return fmt.Errorf("Error putting S3 metric configuration: %s", err) + } + + d.SetId(fmt.Sprintf("%s:%s", bucket, name)) + + return resourceAwsS3BucketMetricRead(d, meta) +} + +func resourceAwsS3BucketMetricDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).s3conn + + bucket, name, err := resourceAwsS3BucketMetricParseID(d.Id()) + if err != nil { + return err + } + + input := &s3.DeleteBucketMetricsConfigurationInput{ + Bucket: aws.String(bucket), + Id: aws.String(name), + } + + log.Printf("[DEBUG] Deleting S3 bucket metric configuration: %s", input) + _, err = conn.DeleteBucketMetricsConfiguration(input) + if err != nil { + if isAWSErr(err, s3.ErrCodeNoSuchBucket, "") || isAWSErr(err, "NoSuchConfiguration", "The specified configuration does not exist.") { + log.Printf("[WARN] %s S3 bucket metrics configuration not found, removing from state.", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error deleting S3 metric configuration: %s", err) + } + + d.SetId("") + return nil +} + +func resourceAwsS3BucketMetricRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).s3conn + + bucket, name, err := resourceAwsS3BucketMetricParseID(d.Id()) + if err != nil { + return err + } + + d.Set("bucket", bucket) + d.Set("name", name) + + input := &s3.GetBucketMetricsConfigurationInput{ + Bucket: aws.String(bucket), + Id: aws.String(name), + } + + log.Printf("[DEBUG] Reading S3 bucket metrics configuration: %s", input) + output, err := conn.GetBucketMetricsConfiguration(input) + if err != nil { + if isAWSErr(err, s3.ErrCodeNoSuchBucket, "") || isAWSErr(err, "NoSuchConfiguration", "The specified configuration does not exist.") { + log.Printf("[WARN] %s S3 bucket metrics configuration not found, removing from state.", d.Id()) + d.SetId("") + return nil + } + return err + } + + if output.MetricsConfiguration.Filter != nil { + if err := d.Set("filter", []interface{}{flattenS3MetricsFilter(output.MetricsConfiguration.Filter)}); err != nil { + return err + } + } + + return nil +} + +func expandS3MetricsFilter(m map[string]interface{}) *s3.MetricsFilter { + var prefix string + if v, ok := m["prefix"]; ok { + prefix = v.(string) + } + + var tags []*s3.Tag + if v, ok := m["tags"]; ok { + tags = tagsFromMapS3(v.(map[string]interface{})) + } + + metricsFilter := &s3.MetricsFilter{} + if prefix != "" && len(tags) > 0 { + metricsFilter.And = &s3.MetricsAndOperator{ + Prefix: aws.String(prefix), + Tags: tags, + } + } else if len(tags) > 1 { + metricsFilter.And = &s3.MetricsAndOperator{ + Tags: tags, + } + } else if len(tags) == 1 { + metricsFilter.Tag = tags[0] + } else { + metricsFilter.Prefix = aws.String(prefix) + } + return metricsFilter +} + +func flattenS3MetricsFilter(metricsFilter *s3.MetricsFilter) map[string]interface{} { + m := make(map[string]interface{}) + + if metricsFilter.And != nil { + and := *metricsFilter.And + if and.Prefix != nil { + m["prefix"] = *and.Prefix + } + if and.Tags != nil { + m["tags"] = tagsToMapS3(and.Tags) + } + } else if metricsFilter.Prefix != nil { + m["prefix"] = *metricsFilter.Prefix + } else if metricsFilter.Tag != nil { + tags := []*s3.Tag{ + metricsFilter.Tag, + } + m["tags"] = tagsToMapS3(tags) + } + return m +} + +func resourceAwsS3BucketMetricParseID(id string) (string, string, error) { + idParts := strings.Split(id, ":") + if len(idParts) != 2 { + return "", "", fmt.Errorf("please make sure the ID is in the form BUCKET:NAME (i.e. my-bucket:EntireBucket") + } + bucket := idParts[0] + name := idParts[1] + return bucket, name, nil +} diff --git a/aws/resource_aws_s3_bucket_metric_test.go b/aws/resource_aws_s3_bucket_metric_test.go new file mode 100644 index 00000000000..8bf31f70f82 --- /dev/null +++ b/aws/resource_aws_s3_bucket_metric_test.go @@ -0,0 +1,705 @@ +package aws + +import ( + "fmt" + "log" + "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/service/s3" +) + +func TestExpandS3MetricsFilter(t *testing.T) { + testCases := []struct { + Config map[string]interface{} + ExpectedS3MetricsFilter *s3.MetricsFilter + }{ + { + Config: map[string]interface{}{ + "prefix": "prefix/", + }, + ExpectedS3MetricsFilter: &s3.MetricsFilter{ + Prefix: aws.String("prefix/"), + }, + }, + { + Config: map[string]interface{}{ + "prefix": "prefix/", + "tags": map[string]interface{}{ + "tag1key": "tag1value", + }, + }, + ExpectedS3MetricsFilter: &s3.MetricsFilter{ + And: &s3.MetricsAndOperator{ + Prefix: aws.String("prefix/"), + Tags: []*s3.Tag{ + &s3.Tag{ + Key: aws.String("tag1key"), + Value: aws.String("tag1value"), + }, + }, + }, + }, + }, + { + Config: map[string]interface{}{ + "prefix": "prefix/", + "tags": map[string]interface{}{ + "tag1key": "tag1value", + "tag2key": "tag2value", + }, + }, + ExpectedS3MetricsFilter: &s3.MetricsFilter{ + And: &s3.MetricsAndOperator{ + Prefix: aws.String("prefix/"), + Tags: []*s3.Tag{ + &s3.Tag{ + Key: aws.String("tag1key"), + Value: aws.String("tag1value"), + }, + &s3.Tag{ + Key: aws.String("tag2key"), + Value: aws.String("tag2value"), + }, + }, + }, + }, + }, + { + Config: map[string]interface{}{ + "tags": map[string]interface{}{ + "tag1key": "tag1value", + }, + }, + ExpectedS3MetricsFilter: &s3.MetricsFilter{ + Tag: &s3.Tag{ + Key: aws.String("tag1key"), + Value: aws.String("tag1value"), + }, + }, + }, + { + Config: map[string]interface{}{ + "tags": map[string]interface{}{ + "tag1key": "tag1value", + "tag2key": "tag2value", + }, + }, + ExpectedS3MetricsFilter: &s3.MetricsFilter{ + And: &s3.MetricsAndOperator{ + Tags: []*s3.Tag{ + &s3.Tag{ + Key: aws.String("tag1key"), + Value: aws.String("tag1value"), + }, + &s3.Tag{ + Key: aws.String("tag2key"), + Value: aws.String("tag2value"), + }, + }, + }, + }, + }, + } + + for i, tc := range testCases { + value := expandS3MetricsFilter(tc.Config) + + // Sort tags by key for consistency + if value.And != nil && value.And.Tags != nil { + sort.Slice(value.And.Tags, func(i, j int) bool { + return *value.And.Tags[i].Key < *value.And.Tags[j].Key + }) + } + + // Convert to strings to avoid dealing with pointers + valueS := fmt.Sprintf("%v", value) + expectedValueS := fmt.Sprintf("%v", tc.ExpectedS3MetricsFilter) + + if valueS != expectedValueS { + t.Fatalf("Case #%d: Given:\n%s\n\nExpected:\n%s", i, valueS, expectedValueS) + } + } +} + +func TestFlattenS3MetricsFilter(t *testing.T) { + testCases := []struct { + S3MetricsFilter *s3.MetricsFilter + ExpectedConfig map[string]interface{} + }{ + { + S3MetricsFilter: &s3.MetricsFilter{ + Prefix: aws.String("prefix/"), + }, + ExpectedConfig: map[string]interface{}{ + "prefix": "prefix/", + }, + }, + { + S3MetricsFilter: &s3.MetricsFilter{ + And: &s3.MetricsAndOperator{ + Prefix: aws.String("prefix/"), + Tags: []*s3.Tag{ + &s3.Tag{ + Key: aws.String("tag1key"), + Value: aws.String("tag1value"), + }, + }, + }, + }, + ExpectedConfig: map[string]interface{}{ + "prefix": "prefix/", + "tags": map[string]string{ + "tag1key": "tag1value", + }, + }, + }, + { + S3MetricsFilter: &s3.MetricsFilter{ + And: &s3.MetricsAndOperator{ + Prefix: aws.String("prefix/"), + Tags: []*s3.Tag{ + &s3.Tag{ + Key: aws.String("tag1key"), + Value: aws.String("tag1value"), + }, + &s3.Tag{ + Key: aws.String("tag2key"), + Value: aws.String("tag2value"), + }, + }, + }, + }, + ExpectedConfig: map[string]interface{}{ + "prefix": "prefix/", + "tags": map[string]string{ + "tag1key": "tag1value", + "tag2key": "tag2value", + }, + }, + }, + { + S3MetricsFilter: &s3.MetricsFilter{ + Tag: &s3.Tag{ + Key: aws.String("tag1key"), + Value: aws.String("tag1value"), + }, + }, + ExpectedConfig: map[string]interface{}{ + "tags": map[string]string{ + "tag1key": "tag1value", + }, + }, + }, + { + S3MetricsFilter: &s3.MetricsFilter{ + And: &s3.MetricsAndOperator{ + Tags: []*s3.Tag{ + &s3.Tag{ + Key: aws.String("tag1key"), + Value: aws.String("tag1value"), + }, + &s3.Tag{ + Key: aws.String("tag2key"), + Value: aws.String("tag2value"), + }, + }, + }, + }, + ExpectedConfig: map[string]interface{}{ + "tags": map[string]string{ + "tag1key": "tag1value", + "tag2key": "tag2value", + }, + }, + }, + } + + for i, tc := range testCases { + value := flattenS3MetricsFilter(tc.S3MetricsFilter) + + if !reflect.DeepEqual(value, tc.ExpectedConfig) { + t.Fatalf("Case #%d: Given:\n%s\n\nExpected:\n%s", i, value, tc.ExpectedConfig) + } + } +} + +func TestResourceAwsS3BucketMetricParseID(t *testing.T) { + validIds := []string{ + "foo:bar", + "my-bucket:entire-bucket", + } + + for _, s := range validIds { + _, _, err := resourceAwsS3BucketMetricParseID(s) + if err != nil { + t.Fatalf("%s should be a valid S3 bucket metrics configuration id: %s", s, err) + } + } + + invalidIds := []string{ + "", + "foo", + "foo:bar:", + "foo:bar:baz", + "foo::bar", + "foo.bar", + } + + for _, s := range invalidIds { + _, _, err := resourceAwsS3BucketMetricParseID(s) + if err == nil { + t.Fatalf("%s should not be a valid S3 bucket metrics configuration id", s) + } + } +} + +func TestAccAWSS3BucketMetric_basic(t *testing.T) { + var conf s3.MetricsConfiguration + rInt := acctest.RandInt() + resourceName := "aws_s3_bucket_metric.test" + + bucketName := fmt.Sprintf("tf-acc-%d", rInt) + metricName := t.Name() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketMetricDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketMetricsConfigWithoutFilter(bucketName, metricName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketMetricsConfigExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "bucket", bucketName), + resource.TestCheckNoResourceAttr(resourceName, "filter"), + resource.TestCheckResourceAttr(resourceName, "name", metricName), + ), + }, + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSS3BucketMetric_WithFilterPrefix(t *testing.T) { + var conf s3.MetricsConfiguration + rInt := acctest.RandInt() + resourceName := "aws_s3_bucket_metric.test" + + bucketName := fmt.Sprintf("tf-acc-%d", rInt) + metricName := t.Name() + prefix := fmt.Sprintf("prefix-%d/", rInt) + prefixUpdate := fmt.Sprintf("prefix-update-%d/", rInt) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketMetricDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketMetricsConfigWithFilterPrefix(bucketName, metricName, prefix), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketMetricsConfigExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.prefix", prefix), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.%", "0"), + ), + }, + resource.TestStep{ + Config: testAccAWSS3BucketMetricsConfigWithFilterPrefix(bucketName, metricName, prefixUpdate), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketMetricsConfigExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.prefix", prefixUpdate), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.%", "0"), + ), + }, + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSS3BucketMetric_WithFilterPrefixAndMultipleTags(t *testing.T) { + var conf s3.MetricsConfiguration + rInt := acctest.RandInt() + resourceName := "aws_s3_bucket_metric.test" + + bucketName := fmt.Sprintf("tf-acc-%d", rInt) + metricName := t.Name() + prefix := fmt.Sprintf("prefix-%d/", rInt) + prefixUpdate := fmt.Sprintf("prefix-update-%d/", rInt) + tag1 := fmt.Sprintf("tag1-%d", rInt) + tag1Update := fmt.Sprintf("tag1-update-%d", rInt) + tag2 := fmt.Sprintf("tag2-%d", rInt) + tag2Update := fmt.Sprintf("tag2-update-%d", rInt) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketMetricDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketMetricsConfigWithFilterPrefixAndMultipleTags(bucketName, metricName, prefix, tag1, tag2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketMetricsConfigExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.prefix", prefix), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag1", tag1), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag2", tag2), + ), + }, + resource.TestStep{ + Config: testAccAWSS3BucketMetricsConfigWithFilterPrefixAndMultipleTags(bucketName, metricName, prefixUpdate, tag1Update, tag2Update), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketMetricsConfigExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.prefix", prefixUpdate), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag1", tag1Update), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag2", tag2Update), + ), + }, + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSS3BucketMetric_WithFilterPrefixAndSingleTag(t *testing.T) { + var conf s3.MetricsConfiguration + rInt := acctest.RandInt() + resourceName := "aws_s3_bucket_metric.test" + + bucketName := fmt.Sprintf("tf-acc-%d", rInt) + metricName := t.Name() + prefix := fmt.Sprintf("prefix-%d/", rInt) + prefixUpdate := fmt.Sprintf("prefix-update-%d/", rInt) + tag1 := fmt.Sprintf("tag-%d", rInt) + tag1Update := fmt.Sprintf("tag-update-%d", rInt) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketMetricDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketMetricsConfigWithFilterPrefixAndSingleTag(bucketName, metricName, prefix, tag1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketMetricsConfigExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.prefix", prefix), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag1", tag1), + ), + }, + resource.TestStep{ + Config: testAccAWSS3BucketMetricsConfigWithFilterPrefixAndSingleTag(bucketName, metricName, prefixUpdate, tag1Update), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketMetricsConfigExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.prefix", prefixUpdate), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag1", tag1Update), + ), + }, + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSS3BucketMetric_WithFilterMultipleTags(t *testing.T) { + var conf s3.MetricsConfiguration + rInt := acctest.RandInt() + resourceName := "aws_s3_bucket_metric.test" + + bucketName := fmt.Sprintf("tf-acc-%d", rInt) + metricName := t.Name() + tag1 := fmt.Sprintf("tag1-%d", rInt) + tag1Update := fmt.Sprintf("tag1-update-%d", rInt) + tag2 := fmt.Sprintf("tag2-%d", rInt) + tag2Update := fmt.Sprintf("tag2-update-%d", rInt) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketMetricDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketMetricsConfigWithFilterMultipleTags(bucketName, metricName, tag1, tag2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketMetricsConfigExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.prefix", ""), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag1", tag1), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag2", tag2), + ), + }, + resource.TestStep{ + Config: testAccAWSS3BucketMetricsConfigWithFilterMultipleTags(bucketName, metricName, tag1Update, tag2Update), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketMetricsConfigExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.prefix", ""), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag1", tag1Update), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag2", tag2Update), + ), + }, + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSS3BucketMetric_WithFilterSingleTag(t *testing.T) { + var conf s3.MetricsConfiguration + rInt := acctest.RandInt() + resourceName := "aws_s3_bucket_metric.test" + + bucketName := fmt.Sprintf("tf-acc-%d", rInt) + metricName := t.Name() + tag1 := fmt.Sprintf("tag-%d", rInt) + tag1Update := fmt.Sprintf("tag-update-%d", rInt) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketMetricDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketMetricsConfigWithFilterSingleTag(bucketName, metricName, tag1), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketMetricsConfigExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.prefix", ""), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag1", tag1), + ), + }, + resource.TestStep{ + Config: testAccAWSS3BucketMetricsConfigWithFilterSingleTag(bucketName, metricName, tag1Update), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketMetricsConfigExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.prefix", ""), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "filter.0.tags.tag1", tag1Update), + ), + }, + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAWSS3BucketMetricDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).s3conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_s3_bucket_metric" { + continue + } + + bucket, name, err := resourceAwsS3BucketMetricParseID(rs.Primary.ID) + if err != nil { + return err + } + + err = resource.Retry(1*time.Minute, func() *resource.RetryError { + input := &s3.GetBucketMetricsConfigurationInput{ + Bucket: aws.String(bucket), + Id: aws.String(name), + } + log.Printf("[DEBUG] Reading S3 bucket metrics configuration: %s", input) + output, err := conn.GetBucketMetricsConfiguration(input) + if err != nil { + if isAWSErr(err, s3.ErrCodeNoSuchBucket, "") || isAWSErr(err, "NoSuchConfiguration", "The specified configuration does not exist.") { + return nil + } + return resource.NonRetryableError(err) + } + if output.MetricsConfiguration != nil { + return resource.RetryableError(fmt.Errorf("S3 bucket metrics configuration exists: %v", output)) + } + + return nil + }) + + if err != nil { + return err + } + } + return nil +} + +func testAccCheckAWSS3BucketMetricsConfigExists(n string, res *s3.MetricsConfiguration) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No S3 bucket metrics configuration ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).s3conn + bucket, name, err := resourceAwsS3BucketMetricParseID(rs.Primary.ID) + if err != nil { + return err + } + + input := &s3.GetBucketMetricsConfigurationInput{ + Bucket: aws.String(bucket), + Id: aws.String(name), + } + log.Printf("[DEBUG] Reading S3 bucket metrics configuration: %s", input) + output, err := conn.GetBucketMetricsConfiguration(input) + if err != nil { + return err + } + + *res = *output.MetricsConfiguration + + return nil + } +} + +func testAccAWSS3BucketMetricsConfigBucket(name string) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "bucket" { + acl = "public-read" + bucket = "%s" +} +`, name) +} + +func testAccAWSS3BucketMetricsConfigWithFilterPrefix(bucketName, metricName, prefix string) string { + return fmt.Sprintf(` +%s + +resource "aws_s3_bucket_metric" "test" { + bucket = "${aws_s3_bucket.bucket.id}" + name = "%s" + + filter { + prefix = "%s" + } +} +`, testAccAWSS3BucketMetricsConfigBucket(bucketName), metricName, prefix) +} + +func testAccAWSS3BucketMetricsConfigWithFilterPrefixAndMultipleTags(bucketName, metricName, prefix, tag1, tag2 string) string { + return fmt.Sprintf(` +%s + +resource "aws_s3_bucket_metric" "test" { + bucket = "${aws_s3_bucket.bucket.id}" + name = "%s" + + filter { + prefix = "%s" + + tags { + "tag1" = "%s" + "tag2" = "%s" + } + } +} +`, testAccAWSS3BucketMetricsConfigBucket(bucketName), metricName, prefix, tag1, tag2) +} + +func testAccAWSS3BucketMetricsConfigWithFilterPrefixAndSingleTag(bucketName, metricName, prefix, tag string) string { + return fmt.Sprintf(` +%s + +resource "aws_s3_bucket_metric" "test" { + bucket = "${aws_s3_bucket.bucket.id}" + name = "%s" + + filter { + prefix = "%s" + + tags { + "tag1" = "%s" + } + } +} +`, testAccAWSS3BucketMetricsConfigBucket(bucketName), metricName, prefix, tag) +} + +func testAccAWSS3BucketMetricsConfigWithFilterMultipleTags(bucketName, metricName, tag1, tag2 string) string { + return fmt.Sprintf(` +%s + +resource "aws_s3_bucket_metric" "test" { + bucket = "${aws_s3_bucket.bucket.id}" + name = "%s" + + filter { + tags { + "tag1" = "%s" + "tag2" = "%s" + } + } +} +`, testAccAWSS3BucketMetricsConfigBucket(bucketName), metricName, tag1, tag2) +} + +func testAccAWSS3BucketMetricsConfigWithFilterSingleTag(bucketName, metricName, tag string) string { + return fmt.Sprintf(` +%s + +resource "aws_s3_bucket_metric" "test" { + bucket = "${aws_s3_bucket.bucket.id}" + name = "%s" + + filter { + tags { + "tag1" = "%s" + } + } +} +`, testAccAWSS3BucketMetricsConfigBucket(bucketName), metricName, tag) +} + +func testAccAWSS3BucketMetricsConfigWithoutFilter(bucketName, metricName string) string { + return fmt.Sprintf(` +%s + +resource "aws_s3_bucket_metric" "test" { + bucket = "${aws_s3_bucket.bucket.id}" + name = "%s" +} +`, testAccAWSS3BucketMetricsConfigBucket(bucketName), metricName) +} diff --git a/website/aws.erb b/website/aws.erb index 3b974d574fc..f739db4765a 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1193,6 +1193,10 @@ aws_s3_bucket + > + aws_s3_bucket_metric + + > aws_s3_bucket_notification diff --git a/website/docs/r/s3_bucket_metric.html.markdown b/website/docs/r/s3_bucket_metric.html.markdown new file mode 100644 index 00000000000..0eaf8a7ad01 --- /dev/null +++ b/website/docs/r/s3_bucket_metric.html.markdown @@ -0,0 +1,69 @@ +--- +layout: "aws" +page_title: "AWS: aws_s3_bucket_metric" +side_bar_current: "docs-aws-resource-s3-bucket-metric" +description: |- + Provides a S3 bucket metrics configuration resource. +--- + +# aws_s3_bucket_metric + +Provides a S3 bucket [metrics configuration](http://docs.aws.amazon.com/AmazonS3/latest/dev/metrics-configurations.html) resource. + +## Example Usage + +### Add metrics configuration for entire S3 bucket + +```hcl +resource "aws_s3_bucket" "example" { + bucket = "example" +} + +resource "aws_s3_bucket_metric" "example-entire-bucket" { + bucket = "${aws_s3_bucket.example.bucket}" + name = "EntireBucket" +} +``` + +### Add metrics configuration with S3 bucket object filter + +```hcl +resource "aws_s3_bucket" "example" { + bucket = "example" +} + +resource "aws_s3_bucket_metric" "example-filtered" { + bucket = "${aws_s3_bucket.example.bucket}" + name = "ImportantBlueDocuments" + + filter { + prefix = "documents/" + + tags { + priority = "high" + class = "blue" + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `bucket` - (Required) The name of the bucket to put metric configuration. +* `name` - (Required) Unique identifier of the metrics configuration for the bucket. +* `filter` - (Optional) [Object filtering](http://docs.aws.amazon.com/AmazonS3/latest/dev/metrics-configurations.html#metrics-configurations-filter) that accepts a prefix, tags, or a logical AND of prefix and tags (documented below). + +The `filter` metric configuration supports the following: + +* `prefix` - (Optional) Object prefix for filtering (singular). +* `tags` - (Optional) Object tags for filtering (up to 10). + +## Import + +S3 bucket metric configurations can be imported using `bucket:metric`, e.g. + +``` +$ terraform import aws_s3_bucket_metric.my-bucket-entire-bucket my-bucket:EntireBucket +``` From 58fed40eb21475e9b8df90b935a97522e77cae14 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Fri, 16 Feb 2018 16:36:46 -0500 Subject: [PATCH 2/2] resource/aws_s3_bucket_metric: Fix import ordering and spread filter casting across lines --- aws/resource_aws_s3_bucket_metric.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/aws/resource_aws_s3_bucket_metric.go b/aws/resource_aws_s3_bucket_metric.go index 639d82d6207..37ed7f137d0 100644 --- a/aws/resource_aws_s3_bucket_metric.go +++ b/aws/resource_aws_s3_bucket_metric.go @@ -6,11 +6,10 @@ import ( "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/service/s3" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" ) func resourceAwsS3BucketMetric() *schema.Resource { @@ -62,7 +61,9 @@ func resourceAwsS3BucketMetricPut(d *schema.ResourceData, meta interface{}) erro } if v, ok := d.GetOk("filter"); ok { - metricsConfiguration.Filter = expandS3MetricsFilter(v.([]interface{})[0].(map[string]interface{})) + filterList := v.([]interface{}) + filterMap := filterList[0].(map[string]interface{}) + metricsConfiguration.Filter = expandS3MetricsFilter(filterMap) } input := &s3.PutBucketMetricsConfigurationInput{