From 13ea0a01c61416795b0497cee2b9dfba30438e6a Mon Sep 17 00:00:00 2001 From: Joern Barthel Date: Thu, 4 Aug 2016 19:19:43 +0200 Subject: [PATCH 01/26] Added IP ranges from Fastly --- .../providers/fastly/data_source_ip_ranges.go | 68 +++++++++++++++++++ builtin/providers/fastly/provider.go | 3 + 2 files changed, 71 insertions(+) create mode 100644 builtin/providers/fastly/data_source_ip_ranges.go diff --git a/builtin/providers/fastly/data_source_ip_ranges.go b/builtin/providers/fastly/data_source_ip_ranges.go new file mode 100644 index 000000000..bc01cd232 --- /dev/null +++ b/builtin/providers/fastly/data_source_ip_ranges.go @@ -0,0 +1,68 @@ +package fastly + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "sort" + "time" + + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/terraform/helper/schema" +) + +type dataSourceFastlyIPRangesResult struct { + Addresses []string +} + +func dataSourceFastlyIPRanges() *schema.Resource { + return &schema.Resource{ + Read: dataSourceFastlyIPRangesRead, + + Schema: map[string]*schema.Schema{ + "blocks": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceFastlyIPRangesRead(d *schema.ResourceData, meta interface{}) error { + + conn := cleanhttp.DefaultClient() + + log.Printf("[DEBUG] Reading IP ranges") + d.SetId(time.Now().UTC().String()) + + res, err := conn.Get("https://api.fastly.com/public-ip-list") + + if err != nil { + return fmt.Errorf("Error listing IP ranges: %s", err) + } + + defer res.Body.Close() + + data, err := ioutil.ReadAll(res.Body) + + if err != nil { + return fmt.Errorf("Error reading response body: %s", err) + } + + result := new(dataSourceFastlyIPRangesResult) + + if err := json.Unmarshal(data, result); err != nil { + return fmt.Errorf("Error parsing result: %s", err) + } + + sort.Strings(result.Addresses) + + if err := d.Set("blocks", result.Addresses); err != nil { + return fmt.Errorf("Error setting ip ranges: %s", err) + } + + return nil + +} diff --git a/builtin/providers/fastly/provider.go b/builtin/providers/fastly/provider.go index f68c6705b..eee4be8e8 100644 --- a/builtin/providers/fastly/provider.go +++ b/builtin/providers/fastly/provider.go @@ -18,6 +18,9 @@ func Provider() terraform.ResourceProvider { Description: "Fastly API Key from https://app.fastly.com/#account", }, }, + DataSourcesMap: map[string]*schema.Resource{ + "fastly_ip_ranges": dataSourceFastlyIPRanges(), + }, ResourcesMap: map[string]*schema.Resource{ "fastly_service_v1": resourceServiceV1(), }, From 8accef2c27e14d90658730b82d52cfe13875e27b Mon Sep 17 00:00:00 2001 From: Joern Barthel Date: Thu, 4 Aug 2016 19:20:14 +0200 Subject: [PATCH 02/26] Added IP ranges from AWS --- .../aws/data_source_aws_ip_ranges.go | 128 ++++++++++++++++++ builtin/providers/aws/provider.go | 1 + 2 files changed, 129 insertions(+) create mode 100644 builtin/providers/aws/data_source_aws_ip_ranges.go diff --git a/builtin/providers/aws/data_source_aws_ip_ranges.go b/builtin/providers/aws/data_source_aws_ip_ranges.go new file mode 100644 index 000000000..c530d981d --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ip_ranges.go @@ -0,0 +1,128 @@ +package aws + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "sort" + "strings" + "time" + + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/terraform/helper/schema" +) + +type dataSourceAwsIPRangesResult struct { + CreateDate string + Prefixes []dataSourceAwsIPRangesPrefix + SyncToken string +} + +type dataSourceAwsIPRangesPrefix struct { + IpPrefix string `json:"ip_prefix"` + Region string + Service string +} + +func dataSourceAwsIPRanges() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAwsIPRangesRead, + + Schema: map[string]*schema.Schema{ + "blocks": &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "create_date": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "regions": &schema.Schema{ + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + "services": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "sync_token": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceAwsIPRangesRead(d *schema.ResourceData, meta interface{}) error { + + conn := cleanhttp.DefaultClient() + + log.Printf("[DEBUG] Reading IP ranges") + d.SetId(time.Now().UTC().String()) + + res, err := conn.Get("https://ip-ranges.amazonaws.com/ip-ranges.json") + + if err != nil { + return fmt.Errorf("Error listing IP ranges: %s", err) + } + + defer res.Body.Close() + + data, err := ioutil.ReadAll(res.Body) + + if err != nil { + return fmt.Errorf("Error reading response body: %s", err) + } + + result := new(dataSourceAwsIPRangesResult) + + if err := json.Unmarshal(data, result); err != nil { + return fmt.Errorf("Error parsing result: %s", err) + } + + if err := d.Set("create_date", result.CreateDate); err != nil { + return fmt.Errorf("Error setting create date: %s", err) + } + + if err := d.Set("sync_token", result.SyncToken); err != nil { + return fmt.Errorf("Error setting sync token: %s", err) + } + + var ( + regions = d.Get("regions").(*schema.Set) + services = d.Get("services").(*schema.Set) + noRegionFilter = regions.Len() == 0 + noServiceFilter = services.Len() == 0 + prefixes []string + ) + + for _, e := range result.Prefixes { + + var ( + matchRegion = noRegionFilter || regions.Contains(strings.ToLower(e.Region)) + matchService = noServiceFilter || services.Contains(strings.ToLower(e.Service)) + ) + + if matchRegion && matchService { + prefixes = append(prefixes, e.IpPrefix) + } + + } + + if len(prefixes) == 0 { + log.Printf("[WARN] No ip ranges result from filters") + } + + sort.Strings(prefixes) + + if err := d.Set("blocks", prefixes); err != nil { + return fmt.Errorf("Error setting ip ranges: %s", err) + } + + return nil + +} diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 69e264dd9..e15931225 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -114,6 +114,7 @@ func Provider() terraform.ResourceProvider { "aws_ami": dataSourceAwsAmi(), "aws_availability_zones": dataSourceAwsAvailabilityZones(), "aws_iam_policy_document": dataSourceAwsIamPolicyDocument(), + "aws_ip_ranges": dataSourceAwsIPRanges(), "aws_s3_bucket_object": dataSourceAwsS3BucketObject(), "aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(), }, From e21203a36973ef3dd7ec47b8202da6ea96a3e1f0 Mon Sep 17 00:00:00 2001 From: Joern Barthel Date: Fri, 5 Aug 2016 17:08:22 +0200 Subject: [PATCH 03/26] Changed sync token to int and use it as id. --- .../providers/aws/data_source_aws_ip_ranges.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/builtin/providers/aws/data_source_aws_ip_ranges.go b/builtin/providers/aws/data_source_aws_ip_ranges.go index c530d981d..c71eb0a72 100644 --- a/builtin/providers/aws/data_source_aws_ip_ranges.go +++ b/builtin/providers/aws/data_source_aws_ip_ranges.go @@ -6,8 +6,8 @@ import ( "io/ioutil" "log" "sort" + "strconv" "strings" - "time" "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/terraform/helper/schema" @@ -50,7 +50,7 @@ func dataSourceAwsIPRanges() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, "sync_token": &schema.Schema{ - Type: schema.TypeString, + Type: schema.TypeInt, Computed: true, }, }, @@ -62,7 +62,6 @@ func dataSourceAwsIPRangesRead(d *schema.ResourceData, meta interface{}) error { conn := cleanhttp.DefaultClient() log.Printf("[DEBUG] Reading IP ranges") - d.SetId(time.Now().UTC().String()) res, err := conn.Get("https://ip-ranges.amazonaws.com/ip-ranges.json") @@ -88,7 +87,15 @@ func dataSourceAwsIPRangesRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error setting create date: %s", err) } - if err := d.Set("sync_token", result.SyncToken); err != nil { + syncToken, err := strconv.Atoi(result.SyncToken) + + if err != nil { + return fmt.Errorf("Error while converting sync token: %s", err) + } + + d.SetId(result.SyncToken) + + if err := d.Set("sync_token", syncToken); err != nil { return fmt.Errorf("Error setting sync token: %s", err) } From 67b4b4cbfb453ceba45d39e9c43e187448843d98 Mon Sep 17 00:00:00 2001 From: Joern Barthel Date: Fri, 5 Aug 2016 17:08:50 +0200 Subject: [PATCH 04/26] Use content as id. --- builtin/providers/fastly/data_source_ip_ranges.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/builtin/providers/fastly/data_source_ip_ranges.go b/builtin/providers/fastly/data_source_ip_ranges.go index bc01cd232..080ac081b 100644 --- a/builtin/providers/fastly/data_source_ip_ranges.go +++ b/builtin/providers/fastly/data_source_ip_ranges.go @@ -6,9 +6,10 @@ import ( "io/ioutil" "log" "sort" - "time" + "strconv" "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" ) @@ -35,7 +36,6 @@ func dataSourceFastlyIPRangesRead(d *schema.ResourceData, meta interface{}) erro conn := cleanhttp.DefaultClient() log.Printf("[DEBUG] Reading IP ranges") - d.SetId(time.Now().UTC().String()) res, err := conn.Get("https://api.fastly.com/public-ip-list") @@ -51,6 +51,8 @@ func dataSourceFastlyIPRangesRead(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error reading response body: %s", err) } + d.SetId(strconv.Itoa(hashcode.String(string(data)))) + result := new(dataSourceFastlyIPRangesResult) if err := json.Unmarshal(data, result); err != nil { From 2caae49a3b773217e8efff95fcf2345031e947dc Mon Sep 17 00:00:00 2001 From: Joern Barthel Date: Fri, 5 Aug 2016 17:09:12 +0200 Subject: [PATCH 05/26] Fail if filter yields no results. --- builtin/providers/aws/data_source_aws_ip_ranges.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/providers/aws/data_source_aws_ip_ranges.go b/builtin/providers/aws/data_source_aws_ip_ranges.go index c71eb0a72..b03fc3632 100644 --- a/builtin/providers/aws/data_source_aws_ip_ranges.go +++ b/builtin/providers/aws/data_source_aws_ip_ranges.go @@ -121,7 +121,7 @@ func dataSourceAwsIPRangesRead(d *schema.ResourceData, meta interface{}) error { } if len(prefixes) == 0 { - log.Printf("[WARN] No ip ranges result from filters") + return fmt.Errorf(" No IP ranges result from filters") } sort.Strings(prefixes) From 9f565285d1eb281ca00b62ca17c8ad4e3e69db77 Mon Sep 17 00:00:00 2001 From: Joern Barthel Date: Fri, 5 Aug 2016 17:12:28 +0200 Subject: [PATCH 06/26] Renamed blocks to cidr_blocks. --- builtin/providers/aws/data_source_aws_ip_ranges.go | 4 ++-- builtin/providers/fastly/data_source_ip_ranges.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/providers/aws/data_source_aws_ip_ranges.go b/builtin/providers/aws/data_source_aws_ip_ranges.go index b03fc3632..799af0ca7 100644 --- a/builtin/providers/aws/data_source_aws_ip_ranges.go +++ b/builtin/providers/aws/data_source_aws_ip_ranges.go @@ -30,7 +30,7 @@ func dataSourceAwsIPRanges() *schema.Resource { Read: dataSourceAwsIPRangesRead, Schema: map[string]*schema.Schema{ - "blocks": &schema.Schema{ + "cidr_blocks": &schema.Schema{ Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, @@ -126,7 +126,7 @@ func dataSourceAwsIPRangesRead(d *schema.ResourceData, meta interface{}) error { sort.Strings(prefixes) - if err := d.Set("blocks", prefixes); err != nil { + if err := d.Set("cidr_blocks", prefixes); err != nil { return fmt.Errorf("Error setting ip ranges: %s", err) } diff --git a/builtin/providers/fastly/data_source_ip_ranges.go b/builtin/providers/fastly/data_source_ip_ranges.go index 080ac081b..cc418465c 100644 --- a/builtin/providers/fastly/data_source_ip_ranges.go +++ b/builtin/providers/fastly/data_source_ip_ranges.go @@ -22,7 +22,7 @@ func dataSourceFastlyIPRanges() *schema.Resource { Read: dataSourceFastlyIPRangesRead, Schema: map[string]*schema.Schema{ - "blocks": &schema.Schema{ + "cidr_blocks": &schema.Schema{ Type: schema.TypeList, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, @@ -61,7 +61,7 @@ func dataSourceFastlyIPRangesRead(d *schema.ResourceData, meta interface{}) erro sort.Strings(result.Addresses) - if err := d.Set("blocks", result.Addresses); err != nil { + if err := d.Set("cidr_blocks", result.Addresses); err != nil { return fmt.Errorf("Error setting ip ranges: %s", err) } From 67bf13fccf21d7c4411b69761d58fd53c222631d Mon Sep 17 00:00:00 2001 From: Joern Barthel Date: Fri, 5 Aug 2016 17:12:46 +0200 Subject: [PATCH 07/26] Added documentation. --- .../providers/aws/d/ip_ranges.html.markdown | 59 +++++++++++++++++++ .../fastly/d/ip_ranges.html.markdown | 37 ++++++++++++ website/source/layouts/aws.erb | 3 + website/source/layouts/fastly.erb | 9 +++ 4 files changed, 108 insertions(+) create mode 100644 website/source/docs/providers/aws/d/ip_ranges.html.markdown create mode 100644 website/source/docs/providers/fastly/d/ip_ranges.html.markdown diff --git a/website/source/docs/providers/aws/d/ip_ranges.html.markdown b/website/source/docs/providers/aws/d/ip_ranges.html.markdown new file mode 100644 index 000000000..f7fe90a8c --- /dev/null +++ b/website/source/docs/providers/aws/d/ip_ranges.html.markdown @@ -0,0 +1,59 @@ +--- +layout: "aws" +page_title: "AWS: aws_ip_ranges" +sidebar_current: "docs-aws-datasource-ip_ranges" +description: |- + Get information on AWS IP ranges. +--- + +# aws\_ip_ranges + +Use this data source to get the [IP ranges][1] of various AWS products and services. + +## Example Usage + +``` +data "aws_ip_ranges" "european_ec2" { + regions = [ "eu-west-1", "eu-central-1" ] + services = [ "ec2" ] +} + +resource "aws_security_group" "from_europe" { + + name = "from_europe" + + ingress { + from_port = "443" + to_port = "443" + protocol = "tcp" + cidr_blocks = [ "${data.aws_ip_ranges.european_ec2.blocks}" ] + } + + tags { + CreateDate = "${data.aws_ip_ranges.european_ec2.create_date}" + SyncToken = "${data.aws_ip_ranges.european_ec2.sync_token}" + } + +} +``` + +## Argument Reference + +* `regions` - (Optional) Filter IP ranges by regions (or include all regions, if +omitted). Valid items are `global` (for `cloudfront`) as well as all AWS regions +(e.g. `eu-central-1`) + +* `services` - (Required) Filter IP ranges by services. Valid items are `amazon` +(for amazon.com), `cloudfront`, `ec2`, `route53` and `route53_healthchecks`. + +~> **NOTE:** If the specified combination of regions and services does not yield any +CIDR blocks, Terraform will fail. + +## Attributes Reference + +* `cidr_blocks` - The lexically ordered list of CIDR blocks. +* `create_date` - The publication time of the IP ranges (e.g. `2016-08-03-23-46-05`). +* `sync_token` - The publication time of the IP ranges, in Unix epoch time format + (e.g. `1470267965`). + +[1]: http://docs.aws.amazon.com/general/latest/gr/aws-ip-ranges.html diff --git a/website/source/docs/providers/fastly/d/ip_ranges.html.markdown b/website/source/docs/providers/fastly/d/ip_ranges.html.markdown new file mode 100644 index 000000000..75cd59b00 --- /dev/null +++ b/website/source/docs/providers/fastly/d/ip_ranges.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "fastly" +page_title: "Fastly: fastly_ip_ranges" +sidebar_current: "docs-fastly-datasource-ip_ranges" +description: |- + Get information on Fastly IP ranges. +--- + +# fastly\_ip_ranges + +Use this data source to get the [IP ranges][1] of Fastly edge nodes. + +## Example Usage + +``` +data "fastly_ip_ranges" "fastly" { +} + +resource "aws_security_group" "from_fastly" { + + name = "from_fastly" + + ingress { + from_port = "443" + to_port = "443" + protocol = "tcp" + cidr_blocks = [ "${data.fastly_ip_ranges.fastly.cidr_blocks}" ] + } + +} +``` + +## Attributes Reference + +* `cidr_blocks` - The lexically ordered list of CIDR blocks. + +[1]: https://docs.fastly.com/guides/securing-communications/accessing-fastlys-ip-ranges diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index c4765aafe..ed7e12a0d 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -25,6 +25,9 @@ > aws_iam_policy_document + > + aws_ip_ranges + > aws_s3_bucket_object diff --git a/website/source/layouts/fastly.erb b/website/source/layouts/fastly.erb index 1958464a0..b1cd59d34 100644 --- a/website/source/layouts/fastly.erb +++ b/website/source/layouts/fastly.erb @@ -10,6 +10,15 @@ Fastly Provider + > + Data Sources + + + > Resources From d0278ecf0f69928ff55173e584dfd113e99bc970 Mon Sep 17 00:00:00 2001 From: Joern Barthel Date: Tue, 9 Aug 2016 14:42:09 +0200 Subject: [PATCH 08/26] =?UTF-8?q?Normalize=20sets,=20don=E2=80=99t=20check?= =?UTF-8?q?=20for=20missing=20services.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aws/data_source_aws_ip_ranges.go | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/builtin/providers/aws/data_source_aws_ip_ranges.go b/builtin/providers/aws/data_source_aws_ip_ranges.go index 799af0ca7..32e9d8988 100644 --- a/builtin/providers/aws/data_source_aws_ip_ranges.go +++ b/builtin/providers/aws/data_source_aws_ip_ranges.go @@ -99,19 +99,35 @@ func dataSourceAwsIPRangesRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error setting sync token: %s", err) } + get := func(key string) *schema.Set { + + set := d.Get(key).(*schema.Set) + + for _, e := range set.List() { + + s := e.(string) + + set.Remove(s) + set.Add(strings.ToLower(s)) + + } + + return set + + } + var ( - regions = d.Get("regions").(*schema.Set) - services = d.Get("services").(*schema.Set) - noRegionFilter = regions.Len() == 0 - noServiceFilter = services.Len() == 0 - prefixes []string + regions = get("regions") + services = get("services") + noRegionFilter = regions.Len() == 0 + prefixes []string ) for _, e := range result.Prefixes { var ( matchRegion = noRegionFilter || regions.Contains(strings.ToLower(e.Region)) - matchService = noServiceFilter || services.Contains(strings.ToLower(e.Service)) + matchService = services.Contains(strings.ToLower(e.Service)) ) if matchRegion && matchService { From 8ea400b442828bc1b461384f1babba118853ffd2 Mon Sep 17 00:00:00 2001 From: Joern Barthel Date: Tue, 9 Aug 2016 14:42:19 +0200 Subject: [PATCH 09/26] Added tests. --- .../aws/data_source_aws_ip_ranges_test.go | 128 ++++++++++++++++++ .../fastly/data_source_ip_ranges_test.go | 73 ++++++++++ 2 files changed, 201 insertions(+) create mode 100644 builtin/providers/aws/data_source_aws_ip_ranges_test.go create mode 100644 builtin/providers/fastly/data_source_ip_ranges_test.go diff --git a/builtin/providers/aws/data_source_aws_ip_ranges_test.go b/builtin/providers/aws/data_source_aws_ip_ranges_test.go new file mode 100644 index 000000000..5e8f4b13d --- /dev/null +++ b/builtin/providers/aws/data_source_aws_ip_ranges_test.go @@ -0,0 +1,128 @@ +package aws + +import ( + "fmt" + "net" + "regexp" + "sort" + "strconv" + "testing" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSIPRanges(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSIPRangesConfig, + Check: resource.ComposeTestCheckFunc( + testAccAWSIPRanges("data.aws_ip_ranges.some"), + ), + }, + }, + }) +} + +func testAccAWSIPRanges(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + r := s.RootModule().Resources[n] + a := r.Primary.Attributes + + var ( + cidrBlockSize int + createDate time.Time + err error + syncToken int + ) + + if cidrBlockSize, err = strconv.Atoi(a["cidr_blocks.#"]); err != nil { + return err + } + + if cidrBlockSize < 10 { + return fmt.Errorf("cidr_blocks for eu-west-1 seem suspiciously low: %d", cidrBlockSize) + } + + if createDate, err = time.Parse("2006-01-02-15-04-05", a["create_date"]); err != nil { + return err + } + + if syncToken, err = strconv.Atoi(a["sync_token"]); err != nil { + return err + } + + if syncToken != int(createDate.Unix()) { + return fmt.Errorf("sync_token %d does not match create_date %s", syncToken, createDate) + } + + var cidrBlocks sort.StringSlice = make([]string, cidrBlockSize) + + for i := range make([]string, cidrBlockSize) { + + block := a[fmt.Sprintf("cidr_blocks.%d", i)] + + if _, _, err := net.ParseCIDR(block); err != nil { + return fmt.Errorf("malformed CIDR block %s: %s", block, err) + } + + cidrBlocks[i] = block + + } + + if !sort.IsSorted(cidrBlocks) { + return fmt.Errorf("unexpected order of cidr_blocks: %s", cidrBlocks) + } + + var ( + regionMember = regexp.MustCompile(`regions\.\d+`) + regions, services int + serviceMember = regexp.MustCompile(`services\.\d+`) + ) + + for k, v := range a { + + if regionMember.MatchString(k) { + + if !(v == "eu-west-1" || v == "EU-central-1") { + return fmt.Errorf("unexpected region %s", v) + } + + regions = regions + 1 + + } + + if serviceMember.MatchString(k) { + + if v != "EC2" { + return fmt.Errorf("unexpected service %s", v) + } + + services = services + 1 + } + + } + + if regions != 2 { + return fmt.Errorf("unexpected number of regions: %d", regions) + } + + if services != 1 { + return fmt.Errorf("unexpected number of services: %d", services) + } + + return nil + } +} + +const testAccAWSIPRangesConfig = ` +data "aws_ip_ranges" "some" { + regions = [ "eu-west-1", "EU-central-1" ] + services = [ "EC2" ] +} +` diff --git a/builtin/providers/fastly/data_source_ip_ranges_test.go b/builtin/providers/fastly/data_source_ip_ranges_test.go new file mode 100644 index 000000000..26e4d8f56 --- /dev/null +++ b/builtin/providers/fastly/data_source_ip_ranges_test.go @@ -0,0 +1,73 @@ +package fastly + +import ( + "fmt" + "net" + "sort" + "strconv" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccFastlyIPRanges(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccFastlyIPRangesConfig, + Check: resource.ComposeTestCheckFunc( + testAccFastlyIPRanges("data.fastly_ip_ranges.some"), + ), + }, + }, + }) +} + +func testAccFastlyIPRanges(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + r := s.RootModule().Resources[n] + a := r.Primary.Attributes + + var ( + cidrBlockSize int + err error + ) + + if cidrBlockSize, err = strconv.Atoi(a["cidr_blocks.#"]); err != nil { + return err + } + + if cidrBlockSize < 10 { + return fmt.Errorf("cidr_blocks seem suspiciously low: %d", cidrBlockSize) + } + + var cidrBlocks sort.StringSlice = make([]string, cidrBlockSize) + + for i := range make([]string, cidrBlockSize) { + + block := a[fmt.Sprintf("cidr_blocks.%d", i)] + + if _, _, err := net.ParseCIDR(block); err != nil { + return fmt.Errorf("malformed CIDR block %s: %s", block, err) + } + + cidrBlocks[i] = block + + } + + if !sort.IsSorted(cidrBlocks) { + return fmt.Errorf("unexpected order of cidr_blocks: %s", cidrBlocks) + } + + return nil + } +} + +const testAccFastlyIPRangesConfig = ` +data "fastly_ip_ranges" "some" { +} +` From e77690e47bcaa5f86b72048836714bc4180b7949 Mon Sep 17 00:00:00 2001 From: f440 Date: Wed, 10 Aug 2016 00:01:16 +0900 Subject: [PATCH 10/26] Fix broken documents --- .../docs/providers/aws/r/opsworks_permission.html.markdown | 4 ++-- .../docs/providers/aws/r/opsworks_user_profile.html.markdown | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/website/source/docs/providers/aws/r/opsworks_permission.html.markdown b/website/source/docs/providers/aws/r/opsworks_permission.html.markdown index 3739a35be..6d7d93a09 100644 --- a/website/source/docs/providers/aws/r/opsworks_permission.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_permission.html.markdown @@ -4,7 +4,7 @@ page_title: "AWS: aws_opsworks_permission" sidebar_current: "docs-aws-resource-opsworks-permission" description: |- Provides an OpsWorks permission resource. -------------------------------------------- +--- # aws\_opsworks\_permission @@ -36,4 +36,4 @@ The following arguments are supported: The following attributes are exported: -* `id` - The computed id of the permission. Please note that this is only used internally to identify the permission. This value is not used in aws. \ No newline at end of file +* `id` - The computed id of the permission. Please note that this is only used internally to identify the permission. This value is not used in aws. diff --git a/website/source/docs/providers/aws/r/opsworks_user_profile.html.markdown b/website/source/docs/providers/aws/r/opsworks_user_profile.html.markdown index 781fae028..f5b6de5c3 100644 --- a/website/source/docs/providers/aws/r/opsworks_user_profile.html.markdown +++ b/website/source/docs/providers/aws/r/opsworks_user_profile.html.markdown @@ -1,10 +1,10 @@ --- layout: "aws" -page_title: "AWS: aws_opsworks_user_profile_" +page_title: "AWS: aws_opsworks_user_profile" sidebar_current: "docs-aws-resource-opsworks-user-profile" description: |- Provides an OpsWorks User Profile resource. ---------------------------------------------- +--- # aws\_opsworks\_user\_profile From 8a6e86d11b9ee4db7ce255a72f47807f9fe2d34f Mon Sep 17 00:00:00 2001 From: f440 Date: Wed, 10 Aug 2016 00:14:49 +0900 Subject: [PATCH 11/26] Sort links in sidebar --- website/source/layouts/aws.erb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index fe4f938fd..6f67e3470 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -567,6 +567,10 @@ OpsWorks Resources From 02fb2e8a21d6c46da7e6d7bf4b449e583a6c1031 Mon Sep 17 00:00:00 2001 From: f440 Date: Wed, 10 Aug 2016 00:15:56 +0900 Subject: [PATCH 12/26] Add links to sidebar --- website/source/layouts/aws.erb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 6f67e3470..6682cad46 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -603,6 +603,10 @@ aws_opsworks_nodejs_app_layer + > + aws_opsworks_permission + + > aws_opsworks_php_app_layer @@ -619,6 +623,10 @@ aws_opsworks_static_web_layer + > + aws_opsworks_user_profile + + From f3005f098470ddfdd053a5d0778eac1a9b202af0 Mon Sep 17 00:00:00 2001 From: Abhishek L Date: Tue, 9 Aug 2016 18:01:11 +0200 Subject: [PATCH 13/26] doc: openstack fix minor typo s/ommitted/omitted --- website/source/docs/providers/openstack/index.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/providers/openstack/index.html.markdown b/website/source/docs/providers/openstack/index.html.markdown index c9e23a765..c02bce24c 100644 --- a/website/source/docs/providers/openstack/index.html.markdown +++ b/website/source/docs/providers/openstack/index.html.markdown @@ -51,7 +51,7 @@ The following arguments are supported: Keystone service. By specifying a token, you do not have to specify a username/password combination, since the token was already created by a username/password out of band of Terraform. - If ommitted, the `OS_AUTH_TOKEN` environment variable is used. + If omitted, the `OS_AUTH_TOKEN` environment variable is used. * `api_key` - (Optional; Required if not using `password`) An API Key is issued by a cloud provider as alternative password. Unless From 34d425fc267d1ec06ac0acf4f4a318a3b7974bc3 Mon Sep 17 00:00:00 2001 From: Joe Topjian Date: Tue, 9 Aug 2016 16:49:46 +0000 Subject: [PATCH 14/26] Missing OS_EXTGW_ID in OpenStack Docs This commit adds a note about the requirement for the OS_EXTGW_ID environment variable in order to run the OpenStack Provider acceptance tests. --- website/source/docs/providers/openstack/index.html.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/source/docs/providers/openstack/index.html.markdown b/website/source/docs/providers/openstack/index.html.markdown index c02bce24c..867b02b1b 100644 --- a/website/source/docs/providers/openstack/index.html.markdown +++ b/website/source/docs/providers/openstack/index.html.markdown @@ -144,6 +144,8 @@ variables must also be set: * `OS_NETWORK_ID` - The UUID of a network in your test environment. +* `OS_EXTGW_ID` - The UUID of the external gateway. + To make development easier, the `builtin/providers/openstack/devstack/deploy.sh` script will assist in installing and configuring a standardized [DevStack](http://docs.openstack.org/developer/devstack/) environment along with From e181d753fee18f0ff04a70c10d78a87d328dea78 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 9 Aug 2016 15:03:07 -0400 Subject: [PATCH 15/26] build: Fix ordering of plugin list --- command/internal_plugin_list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/internal_plugin_list.go b/command/internal_plugin_list.go index e4807b799..e3fc743c3 100644 --- a/command/internal_plugin_list.go +++ b/command/internal_plugin_list.go @@ -61,6 +61,7 @@ import ( ) var InternalProviders = map[string]plugin.ProviderFunc{ + "archive": archiveprovider.Provider, "atlas": atlasprovider.Provider, "aws": awsprovider.Provider, "azure": azureprovider.Provider, @@ -105,7 +106,6 @@ var InternalProviders = map[string]plugin.ProviderFunc{ "ultradns": ultradnsprovider.Provider, "vcd": vcdprovider.Provider, "vsphere": vsphereprovider.Provider, - "archive": archiveprovider.Provider, } var InternalProvisioners = map[string]plugin.ProvisionerFunc{ From 22684200b534c1e88c713903a9853426c494dc0f Mon Sep 17 00:00:00 2001 From: Clint Date: Tue, 9 Aug 2016 14:19:12 -0500 Subject: [PATCH 16/26] provider/aws: Fix line ending errors/diffs with IAM Server Certs (#8074) * provider/aws: Failing IAM Server Cert test with windows line endings * provider/aws: Fix IAM Server Cert issue with line encodings --- .../resource_aws_iam_server_certificate.go | 24 +++++-- ...esource_aws_iam_server_certificate_test.go | 70 +++++++++++++++++++ .../iam-ssl-unix-line-endings.pem | 19 +++++ .../iam-ssl-windows-line-endings.pem | 19 +++++ 4 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 builtin/providers/aws/test-fixtures/iam-ssl-unix-line-endings.pem create mode 100644 builtin/providers/aws/test-fixtures/iam-ssl-windows-line-endings.pem diff --git a/builtin/providers/aws/resource_aws_iam_server_certificate.go b/builtin/providers/aws/resource_aws_iam_server_certificate.go index a3f170c17..28258ef15 100644 --- a/builtin/providers/aws/resource_aws_iam_server_certificate.go +++ b/builtin/providers/aws/resource_aws_iam_server_certificate.go @@ -201,14 +201,30 @@ func normalizeCert(cert interface{}) string { return "" } + var rawCert string switch cert.(type) { case string: - hash := sha1.Sum([]byte(strings.TrimSpace(cert.(string)))) - return hex.EncodeToString(hash[:]) + rawCert = cert.(string) case *string: - hash := sha1.Sum([]byte(strings.TrimSpace(*cert.(*string)))) - return hex.EncodeToString(hash[:]) + rawCert = *cert.(*string) default: return "" } + + cleanVal := sha1.Sum(stripCR([]byte(strings.TrimSpace(rawCert)))) + return hex.EncodeToString(cleanVal[:]) +} + +// strip CRs from raw literals. Lifted from go/scanner/scanner.go +// See https://github.com/golang/go/blob/release-branch.go1.6/src/go/scanner/scanner.go#L479 +func stripCR(b []byte) []byte { + c := make([]byte, len(b)) + i := 0 + for _, ch := range b { + if ch != '\r' { + c[i] = ch + i++ + } + } + return c[:i] } diff --git a/builtin/providers/aws/resource_aws_iam_server_certificate_test.go b/builtin/providers/aws/resource_aws_iam_server_certificate_test.go index fe375e8c0..a6fccb988 100644 --- a/builtin/providers/aws/resource_aws_iam_server_certificate_test.go +++ b/builtin/providers/aws/resource_aws_iam_server_certificate_test.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) @@ -86,6 +87,35 @@ func TestAccAWSIAMServerCertificate_disappears(t *testing.T) { }) } +func TestAccAWSIAMServerCertificate_file(t *testing.T) { + var cert iam.ServerCertificate + + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIAMServerCertificateDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccIAMServerCertConfig_file(rInt, "iam-ssl-unix-line-endings"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert), + testAccCheckAWSServerCertAttributes(&cert), + ), + }, + + resource.TestStep{ + Config: testAccIAMServerCertConfig_file(rInt, "iam-ssl-windows-line-endings"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCertExists("aws_iam_server_certificate.test_cert", &cert), + testAccCheckAWSServerCertAttributes(&cert), + ), + }, + }, + }) +} + func testAccCheckCertExists(n string, cert *iam.ServerCertificate) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -285,3 +315,43 @@ dg+Sd4Wjm89UQoUUoiIcstY7FPbqfBtYKfh4RYHAHV2BwDFqzZCM EOF } ` + +// iam-ssl-unix-line-endings +func testAccIAMServerCertConfig_file(rInt int, fName string) string { + return fmt.Sprintf(` +resource "aws_iam_server_certificate" "test_cert" { + name = "terraform-test-cert-%d" + certificate_body = "${file("test-fixtures/%s.pem")}" + + private_key = < Date: Tue, 9 Aug 2016 14:19:58 -0500 Subject: [PATCH 17/26] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e21dd2dbe..50fed8f54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ BUG FIXES: * provider/aws: Fix `aws_s3_bucket` resource `redirect_all_requests_to` action [GH-7883] * provider/aws: Fix issue updating ElasticBeanstalk Environment Settings [GH-7777] * providers/aws: `aws_rds_cluster` creation timeout bumped to 40 minutes [GH-8052] + * provider/aws: Fix line ending errors/diffs with IAM Server Certs [GH-8074] * provider/google: Use resource specific project when making queries/changes [GH-7029] * provider/google: Fix read for the backend service resource [GH-7476] From 9fa978a45fe86891d1fb7cae05d9773b051e9f57 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Tue, 9 Aug 2016 16:03:05 -0400 Subject: [PATCH 18/26] docs: Fix map key interpolation documentation Previously was recommending the now-invalid dot syntax for map keys, change to using HIL indexing. --- website/source/docs/configuration/interpolation.html.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index 22b8c04d1..18374f5cc 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -27,9 +27,8 @@ will be rendered as a literal `${foo}`. variable name. For example, `${var.foo}` will interpolate the `foo` variable value. If the variable is a map, then you can reference static keys in the map with the syntax -`var.MAP.KEY`. For example, `${var.amis.us-east-1}` would -get the value of the `us-east-1` key within the `amis` variable -that is a map. +`var.MAP["KEY"]`. For example, `${var.amis["us-east-1"]` would +get the value of the `us-east-1` key within the `amis` map variable. **To reference attributes of your own resource**, the syntax is `self.ATTRIBUTE`. For example `${self.private_ip_address}` will From 92d75b263c602d9b57789b21ae8be0a75e1a8516 Mon Sep 17 00:00:00 2001 From: Krzysztof Wilczynski Date: Wed, 10 Aug 2016 08:01:17 +0900 Subject: [PATCH 19/26] Add ability to set Requests Payer in aws_s3_bucket. (#8065) Any S3 Bucket owner may wish to share data but not incur charges associated with others accessing the data. This commit adds an optional "request_payer" attribute to the aws_s3_bucket resource so that the owner of the S3 bucket can specify who should bear the cost of Amazon S3 data transfer. Signed-off-by: Krzysztof Wilczynski --- .../providers/aws/resource_aws_s3_bucket.go | 62 +++++++++- .../aws/resource_aws_s3_bucket_test.go | 108 ++++++++++++++++++ .../providers/aws/r/s3_bucket.html.markdown | 8 +- 3 files changed, 174 insertions(+), 4 deletions(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket.go b/builtin/providers/aws/resource_aws_s3_bucket.go index 840b00d09..0978009a9 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket.go +++ b/builtin/providers/aws/resource_aws_s3_bucket.go @@ -286,8 +286,6 @@ func resourceAwsS3Bucket() *schema.Resource { }, }, - "tags": tagsSchema(), - "force_destroy": &schema.Schema{ Type: schema.TypeBool, Optional: true, @@ -300,6 +298,15 @@ func resourceAwsS3Bucket() *schema.Resource { Computed: true, ValidateFunc: validateS3BucketAccelerationStatus, }, + + "request_payer": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validateS3BucketRequestPayerType, + }, + + "tags": tagsSchema(), }, } } @@ -408,6 +415,12 @@ func resourceAwsS3BucketUpdate(d *schema.ResourceData, meta interface{}) error { } } + if d.HasChange("request_payer") { + if err := resourceAwsS3BucketRequestPayerUpdate(s3conn, d); err != nil { + return err + } + } + return resourceAwsS3BucketRead(d, meta) } @@ -568,6 +581,20 @@ func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error { d.Set("acceleration_status", accelerate.Status) } + // Read the request payer configuration. + payer, err := s3conn.GetBucketRequestPayment(&s3.GetBucketRequestPaymentInput{ + Bucket: aws.String(d.Id()), + }) + if err != nil { + return err + } + log.Printf("[DEBUG] S3 Bucket: %s, read request payer: %v", d.Id(), payer) + if payer.Payer != nil { + if err := d.Set("request_payer", *payer.Payer); err != nil { + return err + } + } + // Read the logging configuration logging, err := s3conn.GetBucketLogging(&s3.GetBucketLoggingInput{ Bucket: aws.String(d.Id()), @@ -575,6 +602,7 @@ func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error { if err != nil { return err } + log.Printf("[DEBUG] S3 Bucket: %s, logging: %v", d.Id(), logging) if v := logging.LoggingEnabled; v != nil { lcl := make([]map[string]interface{}, 0, 1) @@ -1163,6 +1191,26 @@ func resourceAwsS3BucketAccelerationUpdate(s3conn *s3.S3, d *schema.ResourceData return nil } +func resourceAwsS3BucketRequestPayerUpdate(s3conn *s3.S3, d *schema.ResourceData) error { + bucket := d.Get("bucket").(string) + payer := d.Get("request_payer").(string) + + i := &s3.PutBucketRequestPaymentInput{ + Bucket: aws.String(bucket), + RequestPaymentConfiguration: &s3.RequestPaymentConfiguration{ + Payer: aws.String(payer), + }, + } + log.Printf("[DEBUG] S3 put bucket request payer: %#v", i) + + _, err := s3conn.PutBucketRequestPayment(i) + if err != nil { + return fmt.Errorf("Error putting S3 request payer: %s", err) + } + + return nil +} + func resourceAwsS3BucketLifecycleUpdate(s3conn *s3.S3, d *schema.ResourceData) error { bucket := d.Get("bucket").(string) @@ -1370,6 +1418,16 @@ func validateS3BucketAccelerationStatus(v interface{}, k string) (ws []string, e return } +func validateS3BucketRequestPayerType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != s3.PayerRequester && value != s3.PayerBucketOwner { + errors = append(errors, fmt.Errorf( + "%q contains an invalid Request Payer type %q. Valid types are either %q or %q", + k, value, s3.PayerRequester, s3.PayerBucketOwner)) + } + return +} + func expirationHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) diff --git a/builtin/providers/aws/resource_aws_s3_bucket_test.go b/builtin/providers/aws/resource_aws_s3_bucket_test.go index 28dd80f12..25f64479a 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_test.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_test.go @@ -77,6 +77,72 @@ func TestAccAWSS3Bucket_acceleration(t *testing.T) { }) } +func TestAccAWSS3Bucket_RequestPayer(t *testing.T) { + rInt := acctest.RandInt() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketConfigRequestPayerBucketOwner(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"), + resource.TestCheckResourceAttr( + "aws_s3_bucket.bucket", + "request_payer", + "BucketOwner"), + testAccCheckAWSS3RequestPayer( + "aws_s3_bucket.bucket", + "BucketOwner"), + ), + }, + resource.TestStep{ + Config: testAccAWSS3BucketConfigRequestPayerRequester(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketExists("aws_s3_bucket.bucket"), + resource.TestCheckResourceAttr( + "aws_s3_bucket.bucket", + "request_payer", + "Requester"), + testAccCheckAWSS3RequestPayer( + "aws_s3_bucket.bucket", + "Requester"), + ), + }, + }, + }) +} + +func TestResourceAWSS3BucketRequestPayer_validation(t *testing.T) { + _, errors := validateS3BucketRequestPayerType("incorrect", "request_payer") + if len(errors) == 0 { + t.Fatalf("Expected to trigger a validation error") + } + + var testCases = []struct { + Value string + ErrCount int + }{ + { + Value: "Requester", + ErrCount: 0, + }, + { + Value: "BucketOwner", + ErrCount: 0, + }, + } + + for _, tc := range testCases { + _, errors := validateS3BucketRequestPayerType(tc.Value, "request_payer") + if len(errors) != tc.ErrCount { + t.Fatalf("Expected not to trigger a validation error") + } + } +} + func TestAccAWSS3Bucket_Policy(t *testing.T) { rInt := acctest.RandInt() @@ -689,6 +755,28 @@ func testAccCheckAWSS3BucketCors(n string, corsRules []*s3.CORSRule) resource.Te } } +func testAccCheckAWSS3RequestPayer(n, expectedPayer string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, _ := s.RootModule().Resources[n] + conn := testAccProvider.Meta().(*AWSClient).s3conn + + out, err := conn.GetBucketRequestPayment(&s3.GetBucketRequestPaymentInput{ + Bucket: aws.String(rs.Primary.ID), + }) + + if err != nil { + return fmt.Errorf("GetBucketRequestPayment error: %v", err) + } + + if *out.Payer != expectedPayer { + return fmt.Errorf("bad error request payer type, expected: %v, got %v", + expectedPayer, out.Payer) + } + + return nil + } +} + func testAccCheckAWSS3BucketLogging(n, b, p string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, _ := s.RootModule().Resources[n] @@ -844,6 +932,26 @@ resource "aws_s3_bucket" "bucket" { `, randInt) } +func testAccAWSS3BucketConfigRequestPayerBucketOwner(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "bucket" { + bucket = "tf-test-bucket-%d" + acl = "public-read" + request_payer = "BucketOwner" +} +`, randInt) +} + +func testAccAWSS3BucketConfigRequestPayerRequester(randInt int) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "bucket" { + bucket = "tf-test-bucket-%d" + acl = "public-read" + request_payer = "Requester" +} +`, randInt) +} + func testAccAWSS3BucketConfigWithPolicy(randInt int) string { return fmt.Sprintf(` resource "aws_s3_bucket" "bucket" { diff --git a/website/source/docs/providers/aws/r/s3_bucket.html.markdown b/website/source/docs/providers/aws/r/s3_bucket.html.markdown index 821a48192..24cf910b3 100644 --- a/website/source/docs/providers/aws/r/s3_bucket.html.markdown +++ b/website/source/docs/providers/aws/r/s3_bucket.html.markdown @@ -173,8 +173,12 @@ The following arguments are supported: * `logging` - (Optional) A settings of [bucket logging](https://docs.aws.amazon.com/AmazonS3/latest/UG/ManagingBucketLogging.html) (documented below). * `lifecycle_rule` - (Optional) A configuration of [object lifecycle management](http://docs.aws.amazon.com/AmazonS3/latest/dev/object-lifecycle-mgmt.html) (documented below). * `acceleration_status` - (Optional) Sets the accelerate configuration of an existing bucket. Can be `Enabled` or `Suspended`. +* `request_payer` - (Optional) Specifies who should bear the cost of Amazon S3 data transfer. +Can be either `BucketOwner` or `Requester`. By default, the owner of the S3 bucket would incur +the costs of any data transfer. See [Requester Pays Buckets](http://docs.aws.amazon.com/AmazonS3/latest/dev/RequesterPaysBuckets.html) +developer guide for more information. -~> **NOTE:** You cannot use `acceleration_status` in `cn-north-1` or `us-gov-west-1` +~> **NOTE:** You cannot use `acceleration_status` in `cn-north-1` or `us-gov-west-1` The `website` object supports the following: @@ -218,7 +222,7 @@ The `expiration` object supports the following * `date` (Optional) Specifies the date after which you want the corresponding action to take effect. * `days` (Optional) Specifies the number of days after object creation when the specific rule action takes effect. -* `expired_object_delete_marker` (Optional) On a versioned bucket (versioning-enabled or versioning-suspended bucket), you can add this element in the lifecycle configuration to direct Amazon S3 to delete expired object delete markers. +* `expired_object_delete_marker` (Optional) On a versioned bucket (versioning-enabled or versioning-suspended bucket), you can add this element in the lifecycle configuration to direct Amazon S3 to delete expired object delete markers. The `transition` object supports the following From 8925235ef16f3d4ac161ba83ad8c576275138284 Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Wed, 10 Aug 2016 11:06:01 +1200 Subject: [PATCH 20/26] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50fed8f54..927fdf574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ IMPROVEMENTS * provider/aws: Add support for Elasticsearch destination to firehose delivery streams [GH-7839] * provider/aws: Retry AttachInternetGateway and increase timeout on `aws_internet_gateway` [GH-7891] * provider/aws: Add support for Enhanced monitoring to `aws_rds_cluster_instance` [GH-8038] + * provider/aws: Add ability to set Requests Payer in `aws_s3_bucket` [GH-8065] * provider/azurerm: Adds support for uploading blobs to azure storage from local source [GH-7994] * provider/google: allows atomic Cloud DNS record changes [GH-6575] * provider/google: Move URLMap hosts to TypeSet from TypeList [GH-7472] From 41c23b2f04acc2c2c1d3b183caf8c64d4b1698e1 Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Tue, 9 Aug 2016 17:06:38 -0700 Subject: [PATCH 21/26] provider/aws: Various IAM policy normalizations for IAM data source (#6956) * Various string slices are sorted and truncated to strings if they only contain one element. * Sids are now included if they are empty. This is to ensure what is sent to AWS matches what comes back, to prevent recurring diffs even when the policy has changed. --- .../data_source_aws_iam_policy_document.go | 17 ++++-- ...ata_source_aws_iam_policy_document_test.go | 45 +++++---------- builtin/providers/aws/iam_policy_model.go | 55 +++++++++++++------ 3 files changed, 65 insertions(+), 52 deletions(-) diff --git a/builtin/providers/aws/data_source_aws_iam_policy_document.go b/builtin/providers/aws/data_source_aws_iam_policy_document.go index 8d5051f77..5bea111ee 100644 --- a/builtin/providers/aws/data_source_aws_iam_policy_document.go +++ b/builtin/providers/aws/data_source_aws_iam_policy_document.go @@ -150,12 +150,19 @@ func dataSourceAwsIamPolicyDocumentRead(d *schema.ResourceData, meta interface{} return nil } -func dataSourceAwsIamPolicyDocumentReplaceVarsInList(in []string) []string { - out := make([]string, len(in)) - for i, item := range in { - out[i] = dataSourceAwsIamPolicyDocumentVarReplacer.Replace(item) +func dataSourceAwsIamPolicyDocumentReplaceVarsInList(in interface{}) interface{} { + switch v := in.(type) { + case string: + return dataSourceAwsIamPolicyDocumentVarReplacer.Replace(v) + case []string: + out := make([]string, len(v)) + for i, item := range v { + out[i] = dataSourceAwsIamPolicyDocumentVarReplacer.Replace(item) + } + return out + default: + panic("dataSourceAwsIamPolicyDocumentReplaceVarsInList: input not string nor []string") } - return out } func dataSourceAwsIamPolicyDocumentMakeConditions(in []interface{}) IAMPolicyStatementConditionSet { diff --git a/builtin/providers/aws/data_source_aws_iam_policy_document_test.go b/builtin/providers/aws/data_source_aws_iam_policy_document_test.go index 8a2210265..a50a8ae29 100644 --- a/builtin/providers/aws/data_source_aws_iam_policy_document_test.go +++ b/builtin/providers/aws/data_source_aws_iam_policy_document_test.go @@ -75,7 +75,6 @@ data "aws_iam_policy_document" "test" { test = "StringLike" variable = "s3:prefix" values = [ - "", "home/", "home/&{aws:username}/", ] @@ -118,59 +117,45 @@ var testAccAWSIAMPolicyDocumentExpectedJSON = `{ "Sid": "1", "Effect": "Allow", "Action": [ - "s3:GetBucketLocation", - "s3:ListAllMyBuckets" + "s3:ListAllMyBuckets", + "s3:GetBucketLocation" ], - "Resource": [ - "arn:aws:s3:::*" - ] + "Resource": "arn:aws:s3:::*" }, { + "Sid": "", "Effect": "Allow", - "Action": [ - "s3:ListBucket" - ], - "Resource": [ - "arn:aws:s3:::foo" - ], + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::foo", "NotPrincipal": { - "AWS": [ - "arn:blahblah:example" - ] + "AWS": "arn:blahblah:example" }, "Condition": { "StringLike": { "s3:prefix": [ - "", - "home/", - "home/${aws:username}/" + "home/${aws:username}/", + "home/" ] } } }, { + "Sid": "", "Effect": "Allow", - "Action": [ - "s3:*" - ], + "Action": "s3:*", "Resource": [ "arn:aws:s3:::foo/home/${aws:username}/*", "arn:aws:s3:::foo/home/${aws:username}" ], "Principal": { - "AWS": [ - "arn:blahblah:example" - ] + "AWS": "arn:blahblah:example" } }, { + "Sid": "", "Effect": "Deny", - "NotAction": [ - "s3:*" - ], - "NotResource": [ - "arn:aws:s3:::*" - ] + "NotAction": "s3:*", + "NotResource": "arn:aws:s3:::*" } ] }` diff --git a/builtin/providers/aws/iam_policy_model.go b/builtin/providers/aws/iam_policy_model.go index 56ffc9d5c..59192fbf1 100644 --- a/builtin/providers/aws/iam_policy_model.go +++ b/builtin/providers/aws/iam_policy_model.go @@ -2,6 +2,7 @@ package aws import ( "encoding/json" + "sort" ) type IAMPolicyDoc struct { @@ -11,12 +12,12 @@ type IAMPolicyDoc struct { } type IAMPolicyStatement struct { - Sid string `json:",omitempty"` + Sid string Effect string `json:",omitempty"` - Actions []string `json:"Action,omitempty"` - NotActions []string `json:"NotAction,omitempty"` - Resources []string `json:"Resource,omitempty"` - NotResources []string `json:"NotResource,omitempty"` + Actions interface{} `json:"Action,omitempty"` + NotActions interface{} `json:"NotAction,omitempty"` + Resources interface{} `json:"Resource,omitempty"` + NotResources interface{} `json:"NotResource,omitempty"` Principals IAMPolicyStatementPrincipalSet `json:"Principal,omitempty"` NotPrincipals IAMPolicyStatementPrincipalSet `json:"NotPrincipal,omitempty"` Conditions IAMPolicyStatementConditionSet `json:"Condition,omitempty"` @@ -24,51 +25,71 @@ type IAMPolicyStatement struct { type IAMPolicyStatementPrincipal struct { Type string - Identifiers []string + Identifiers interface{} } type IAMPolicyStatementCondition struct { Test string Variable string - Values []string + Values interface{} } type IAMPolicyStatementPrincipalSet []IAMPolicyStatementPrincipal type IAMPolicyStatementConditionSet []IAMPolicyStatementCondition func (ps IAMPolicyStatementPrincipalSet) MarshalJSON() ([]byte, error) { - raw := map[string][]string{} + raw := map[string]interface{}{} for _, p := range ps { - if _, ok := raw[p.Type]; !ok { - raw[p.Type] = make([]string, 0, len(p.Identifiers)) + switch i := p.Identifiers.(type) { + case []string: + if _, ok := raw[p.Type]; !ok { + raw[p.Type] = make([]string, 0, len(i)) + } + sort.Sort(sort.Reverse(sort.StringSlice(i))) + raw[p.Type] = append(raw[p.Type].([]string), i...) + case string: + raw[p.Type] = i + default: + panic("Unsupported data type for IAMPolicyStatementPrincipalSet") } - raw[p.Type] = append(raw[p.Type], p.Identifiers...) } return json.Marshal(&raw) } func (cs IAMPolicyStatementConditionSet) MarshalJSON() ([]byte, error) { - raw := map[string]map[string][]string{} + raw := map[string]map[string]interface{}{} for _, c := range cs { if _, ok := raw[c.Test]; !ok { - raw[c.Test] = map[string][]string{} + raw[c.Test] = map[string]interface{}{} } - if _, ok := raw[c.Test][c.Variable]; !ok { - raw[c.Test][c.Variable] = make([]string, 0, len(c.Values)) + switch i := c.Values.(type) { + case []string: + if _, ok := raw[c.Test][c.Variable]; !ok { + raw[c.Test][c.Variable] = make([]string, 0, len(i)) + } + sort.Sort(sort.Reverse(sort.StringSlice(i))) + raw[c.Test][c.Variable] = append(raw[c.Test][c.Variable].([]string), i...) + case string: + raw[c.Test][c.Variable] = i + default: + panic("Unsupported data type for IAMPolicyStatementConditionSet") } - raw[c.Test][c.Variable] = append(raw[c.Test][c.Variable], c.Values...) } return json.Marshal(&raw) } -func iamPolicyDecodeConfigStringList(lI []interface{}) []string { +func iamPolicyDecodeConfigStringList(lI []interface{}) interface{} { + if len(lI) == 1 { + return lI[0].(string) + } ret := make([]string, len(lI)) for i, vI := range lI { ret[i] = vI.(string) } + sort.Sort(sort.Reverse(sort.StringSlice(ret))) return ret } From 25f1c6dc02340c1852770d3fba5108aad48fef3e Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Wed, 10 Aug 2016 12:07:46 +1200 Subject: [PATCH 22/26] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 927fdf574..5fd893998 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,8 +33,9 @@ BUG FIXES: * provider/aws: Retry association of IAM Role & instance profile [GH-7938] * provider/aws: Fix `aws_s3_bucket` resource `redirect_all_requests_to` action [GH-7883] * provider/aws: Fix issue updating ElasticBeanstalk Environment Settings [GH-7777] - * providers/aws: `aws_rds_cluster` creation timeout bumped to 40 minutes [GH-8052] + * provider/aws: `aws_rds_cluster` creation timeout bumped to 40 minutes [GH-8052] * provider/aws: Fix line ending errors/diffs with IAM Server Certs [GH-8074] + * provider/aws: Fixing IAM data source policy generation to prevent spurious diffs [GH-6956] * provider/google: Use resource specific project when making queries/changes [GH-7029] * provider/google: Fix read for the backend service resource [GH-7476] From 6c2949fdac4f4192840ae588a0148863d289d46c Mon Sep 17 00:00:00 2001 From: Kraig Amador Date: Tue, 9 Aug 2016 17:16:59 -0700 Subject: [PATCH 23/26] Added aws_iam_role import. Now that we read the assume_role_policy it highlights all of the tests that change this, so I've fixed a bunch of those while i'm in here. (#7617) --- .../providers/aws/import_aws_iam_role_test.go | 28 +++++++ .../resource_aws_iam_instance_profile_test.go | 8 +- ...resource_aws_iam_policy_attachment_test.go | 75 ++----------------- .../providers/aws/resource_aws_iam_role.go | 9 +++ ...rce_aws_iam_role_policy_attachment_test.go | 36 +-------- .../aws/resource_aws_iam_role_policy_test.go | 30 ++++---- .../aws/resource_aws_iam_role_test.go | 45 ++--------- 7 files changed, 76 insertions(+), 155 deletions(-) create mode 100644 builtin/providers/aws/import_aws_iam_role_test.go diff --git a/builtin/providers/aws/import_aws_iam_role_test.go b/builtin/providers/aws/import_aws_iam_role_test.go new file mode 100644 index 000000000..f46cedd56 --- /dev/null +++ b/builtin/providers/aws/import_aws_iam_role_test.go @@ -0,0 +1,28 @@ +package aws + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccAWSRole_importBasic(t *testing.T) { + resourceName := "aws_iam_role.role" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSRoleDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSRoleConfig, + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/aws/resource_aws_iam_instance_profile_test.go b/builtin/providers/aws/resource_aws_iam_instance_profile_test.go index 93001184b..049ccecae 100644 --- a/builtin/providers/aws/resource_aws_iam_instance_profile_test.go +++ b/builtin/providers/aws/resource_aws_iam_instance_profile_test.go @@ -120,8 +120,8 @@ func testAccCheckAWSInstanceProfileExists(n string, res *iam.GetInstanceProfileO const testAccAwsIamInstanceProfileConfig = ` resource "aws_iam_role" "test" { - name = "test" - assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ec2.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}" + name = "test" + assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}" } resource "aws_iam_instance_profile" "test" { @@ -132,8 +132,8 @@ resource "aws_iam_instance_profile" "test" { const testAccAWSInstanceProfilePrefixNameConfig = ` resource "aws_iam_role" "test" { - name = "test" - assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ec2.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}" + name = "test" + assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}" } resource "aws_iam_instance_profile" "test" { diff --git a/builtin/providers/aws/resource_aws_iam_policy_attachment_test.go b/builtin/providers/aws/resource_aws_iam_policy_attachment_test.go index 11e50b0d9..446f38ef6 100644 --- a/builtin/providers/aws/resource_aws_iam_policy_attachment_test.go +++ b/builtin/providers/aws/resource_aws_iam_policy_attachment_test.go @@ -113,22 +113,8 @@ resource "aws_iam_user" "user" { name = "test-user" } resource "aws_iam_role" "role" { - name = "test-role" - assume_role_policy = < Date: Wed, 10 Aug 2016 10:16:31 +0900 Subject: [PATCH 24/26] Fix invalid markdown syntax (#8089) --- website/source/docs/providers/powerdns/r/record.html.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/source/docs/providers/powerdns/r/record.html.markdown b/website/source/docs/providers/powerdns/r/record.html.markdown index 8d9502604..6fb4ab292 100644 --- a/website/source/docs/providers/powerdns/r/record.html.markdown +++ b/website/source/docs/providers/powerdns/r/record.html.markdown @@ -15,6 +15,7 @@ Provides a PowerDNS record resource. Note that PowerDNS internally lowercases certain records (e.g. CNAME and AAAA), which can lead to resources being marked for a change in every singe plan. For the v1 API (PowerDNS version 4): + ``` # Add a record to the zone resource "powerdns_record" "foobar" { @@ -27,6 +28,7 @@ resource "powerdns_record" "foobar" { ``` For the legacy API (PowerDNS version 3.4): + ``` # Add a record to the zone resource "powerdns_record" "foobar" { From f5b46b80e7f5c7f4397b909609350e3ae9b9e705 Mon Sep 17 00:00:00 2001 From: Krzysztof Wilczynski Date: Wed, 10 Aug 2016 13:05:39 +0900 Subject: [PATCH 25/26] Add ability to set canned ACL in aws_s3_bucket_object. (#8091) An S3 Bucket owner may wish to set a canned ACL (as opposite to explicitly set grantees, etc.) for an object. This commit adds an optional "acl" attribute to the aws_s3_bucket_object resource so that the owner of the S3 bucket can specify an appropriate pre-defined ACL to use when creating an object. Signed-off-by: Krzysztof Wilczynski --- .../aws/resource_aws_s3_bucket_object.go | 46 +++++++ .../aws/resource_aws_s3_bucket_object_test.go | 114 ++++++++++++++++++ .../aws/r/s3_bucket_object.html.markdown | 5 +- 3 files changed, 163 insertions(+), 2 deletions(-) diff --git a/builtin/providers/aws/resource_aws_s3_bucket_object.go b/builtin/providers/aws/resource_aws_s3_bucket_object.go index c7ae47d75..2df9d5da0 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_object.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_object.go @@ -6,6 +6,7 @@ import ( "io" "log" "os" + "sort" "strings" "github.com/hashicorp/terraform/helper/schema" @@ -30,6 +31,13 @@ func resourceAwsS3BucketObject() *schema.Resource { ForceNew: true, }, + "acl": &schema.Schema{ + Type: schema.TypeString, + Default: "private", + Optional: true, + ValidateFunc: validateS3BucketObjectAclType, + }, + "cache_control": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -101,6 +109,7 @@ func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) erro bucket := d.Get("bucket").(string) key := d.Get("key").(string) + acl := d.Get("acl").(string) var body io.ReadSeeker if v, ok := d.GetOk("source"); ok { @@ -131,6 +140,7 @@ func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) erro putInput := &s3.PutObjectInput{ Bucket: aws.String(bucket), Key: aws.String(key), + ACL: aws.String(acl), Body: body, } @@ -251,3 +261,39 @@ func resourceAwsS3BucketObjectDelete(d *schema.ResourceData, meta interface{}) e return nil } + +func validateS3BucketObjectAclType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + cannedAcls := map[string]bool{ + s3.ObjectCannedACLPrivate: true, + s3.ObjectCannedACLPublicRead: true, + s3.ObjectCannedACLPublicReadWrite: true, + s3.ObjectCannedACLAuthenticatedRead: true, + s3.ObjectCannedACLAwsExecRead: true, + s3.ObjectCannedACLBucketOwnerRead: true, + s3.ObjectCannedACLBucketOwnerFullControl: true, + } + + sentenceJoin := func(m map[string]bool) string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, fmt.Sprintf("%q", k)) + } + sort.Strings(keys) + + length := len(keys) + words := make([]string, length) + copy(words, keys) + + words[length-1] = fmt.Sprintf("or %s", words[length-1]) + return strings.Join(words, ", ") + } + + if _, ok := cannedAcls[value]; !ok { + errors = append(errors, fmt.Errorf( + "%q contains an invalid canned ACL type %q. Valid types are either %s", + k, value, sentenceJoin(cannedAcls))) + } + return +} diff --git a/builtin/providers/aws/resource_aws_s3_bucket_object_test.go b/builtin/providers/aws/resource_aws_s3_bucket_object_test.go index 63ccf6861..d88b3c99d 100644 --- a/builtin/providers/aws/resource_aws_s3_bucket_object_test.go +++ b/builtin/providers/aws/resource_aws_s3_bucket_object_test.go @@ -4,6 +4,8 @@ import ( "fmt" "io/ioutil" "os" + "reflect" + "sort" "testing" "github.com/hashicorp/terraform/helper/acctest" @@ -265,6 +267,104 @@ func TestAccAWSS3BucketObject_kms(t *testing.T) { }) } +func TestAccAWSS3BucketObject_acl(t *testing.T) { + rInt := acctest.RandInt() + var obj s3.GetObjectOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSS3BucketObjectDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSS3BucketObjectConfig_acl(rInt, "private"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketObjectExists( + "aws_s3_bucket_object.object", &obj), + resource.TestCheckResourceAttr( + "aws_s3_bucket_object.object", + "acl", + "private"), + testAccCheckAWSS3BucketObjectAcl( + "aws_s3_bucket_object.object", + []string{"FULL_CONTROL"}), + ), + }, + resource.TestStep{ + Config: testAccAWSS3BucketObjectConfig_acl(rInt, "public-read"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSS3BucketObjectExists( + "aws_s3_bucket_object.object", + &obj), + resource.TestCheckResourceAttr( + "aws_s3_bucket_object.object", + "acl", + "public-read"), + testAccCheckAWSS3BucketObjectAcl( + "aws_s3_bucket_object.object", + []string{"FULL_CONTROL", "READ"}), + ), + }, + }, + }) +} + +func TestResourceAWSS3BucketObjectAcl_validation(t *testing.T) { + _, errors := validateS3BucketObjectAclType("incorrect", "acl") + if len(errors) == 0 { + t.Fatalf("Expected to trigger a validation error") + } + + var testCases = []struct { + Value string + ErrCount int + }{ + { + Value: "public-read", + ErrCount: 0, + }, + { + Value: "public-read-write", + ErrCount: 0, + }, + } + + for _, tc := range testCases { + _, errors := validateS3BucketObjectAclType(tc.Value, "acl") + if len(errors) != tc.ErrCount { + t.Fatalf("Expected not to trigger a validation error") + } + } +} + +func testAccCheckAWSS3BucketObjectAcl(n string, expectedPerms []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, _ := s.RootModule().Resources[n] + s3conn := testAccProvider.Meta().(*AWSClient).s3conn + + out, err := s3conn.GetObjectAcl(&s3.GetObjectAclInput{ + Bucket: aws.String(rs.Primary.Attributes["bucket"]), + Key: aws.String(rs.Primary.Attributes["key"]), + }) + + if err != nil { + return fmt.Errorf("GetObjectAcl error: %v", err) + } + + var perms []string + for _, v := range out.Grants { + perms = append(perms, *v.Permission) + } + sort.Strings(perms) + + if !reflect.DeepEqual(perms, expectedPerms) { + return fmt.Errorf("Expected ACL permissions to be %v, got %v", expectedPerms, perms) + } + + return nil + } +} + func testAccAWSS3BucketObjectConfigSource(randInt int, source string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "object_bucket" { @@ -358,3 +458,17 @@ resource "aws_s3_bucket_object" "object" { } `, randInt) } + +func testAccAWSS3BucketObjectConfig_acl(randInt int, acl string) string { + return fmt.Sprintf(` +resource "aws_s3_bucket" "object_bucket" { + bucket = "tf-object-test-bucket-%d" +} +resource "aws_s3_bucket_object" "object" { + bucket = "${aws_s3_bucket.object_bucket.bucket}" + key = "test-key" + content = "some_bucket_content" + acl = "%s" +} +`, randInt, acl) +} diff --git a/website/source/docs/providers/aws/r/s3_bucket_object.html.markdown b/website/source/docs/providers/aws/r/s3_bucket_object.html.markdown index c34997c08..fc7f95b53 100644 --- a/website/source/docs/providers/aws/r/s3_bucket_object.html.markdown +++ b/website/source/docs/providers/aws/r/s3_bucket_object.html.markdown @@ -52,14 +52,15 @@ The following arguments are supported: * `key` - (Required) The name of the object once it is in the bucket. * `source` - (Required) The path to the source file being uploaded to the bucket. * `content` - (Required unless `source` given) The literal content being uploaded to the bucket. +* `acl` - (Optional) The [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. Defaults to "private". * `cache_control` - (Optional) Specifies caching behavior along the request/reply chain Read [w3c cache_control](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) for further details. * `content_disposition` - (Optional) Specifies presentational information for the object. Read [wc3 content_disposition](http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1) for further information. * `content_encoding` - (Optional) Specifies what content encodings have been applied to the object and thus what decoding mechanisms must be applied to obtain the media-type referenced by the Content-Type header field. Read [w3c content encoding](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11) for further information. * `content_language` - (Optional) The language the content is in e.g. en-US or en-GB. * `content_type` - (Optional) A standard MIME type describing the format of the object data, e.g. application/octet-stream. All Valid MIME Types are valid for this input. -* `etag` - (Optional) Used to trigger updates. The only meaningful value is `${md5(file("path/to/file"))}`. +* `etag` - (Optional) Used to trigger updates. The only meaningful value is `${md5(file("path/to/file"))}`. This attribute is not compatible with `kms_key_id` -* `kms_key_id` - (Optional) Specifies the AWS KMS Key ID to use for object encryption. +* `kms_key_id` - (Optional) Specifies the AWS KMS Key ID to use for object encryption. This value is a fully qualified **ARN** of the KMS Key. If using `aws_kms_key`, use the exported `arn` attribute: `kms_key_id = "${aws_kms_key.foo.arn}"` From 6b477e888ac20f2167a5434bc75df6dedb7b6b9e Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Wed, 10 Aug 2016 16:07:32 +1200 Subject: [PATCH 26/26] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fd893998..b2826b55f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ IMPROVEMENTS * provider/aws: Retry AttachInternetGateway and increase timeout on `aws_internet_gateway` [GH-7891] * provider/aws: Add support for Enhanced monitoring to `aws_rds_cluster_instance` [GH-8038] * provider/aws: Add ability to set Requests Payer in `aws_s3_bucket` [GH-8065] + * provider/aws: Add ability to set canned ACL in `aws_s3_bucket_object` [GH-8091] * provider/azurerm: Adds support for uploading blobs to azure storage from local source [GH-7994] * provider/google: allows atomic Cloud DNS record changes [GH-6575] * provider/google: Move URLMap hosts to TypeSet from TypeList [GH-7472]