This commit is contained in:
Giovanni Paolo Gibilisco 2016-08-10 14:10:29 +02:00
commit 7bb7508c86
37 changed files with 1169 additions and 229 deletions

View File

@ -6,6 +6,8 @@ FEATURES:
* **New Resource:** `aws_load_balancer_policy` [GH-7458]
* **New Resource:** `aws_load_balancer_backend_server_policy` [GH-7458]
* **New Resource:** `aws_load_balancer_listener_policy` [GH-7458]
* **New Data Source:** `aws_ip_ranges` [GH-7984]
* **New Data Source:** `fastly_ip_ranges` [GH-7984]
IMPROVEMENTS
* provider/aws: Introduce `aws_elasticsearch_domain` `elasticsearch_version` field (to specify ES version) [GH-7860]
@ -14,6 +16,8 @@ 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/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]
@ -30,7 +34,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]

View File

@ -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 {

View File

@ -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:::*"
}
]
}`

View File

@ -0,0 +1,151 @@
package aws
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"sort"
"strconv"
"strings"
"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{
"cidr_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.TypeInt,
Computed: true,
},
},
}
}
func dataSourceAwsIPRangesRead(d *schema.ResourceData, meta interface{}) error {
conn := cleanhttp.DefaultClient()
log.Printf("[DEBUG] Reading IP ranges")
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)
}
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)
}
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 = 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 = services.Contains(strings.ToLower(e.Service))
)
if matchRegion && matchService {
prefixes = append(prefixes, e.IpPrefix)
}
}
if len(prefixes) == 0 {
return fmt.Errorf(" No IP ranges result from filters")
}
sort.Strings(prefixes)
if err := d.Set("cidr_blocks", prefixes); err != nil {
return fmt.Errorf("Error setting ip ranges: %s", err)
}
return nil
}

View File

@ -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" ]
}
`

View File

@ -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
}

View File

@ -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,
},
},
})
}

View File

@ -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(),
},

View File

@ -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" {

View File

@ -113,22 +113,8 @@ resource "aws_iam_user" "user" {
name = "test-user"
}
resource "aws_iam_role" "role" {
name = "test-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
name = "test-role"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_group" "group" {
@ -174,61 +160,16 @@ resource "aws_iam_user" "user3" {
name = "test-user3"
}
resource "aws_iam_role" "role" {
name = "test-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
name = "test-role"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
EOF
}
resource "aws_iam_role" "role2" {
name = "test-role2"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
name = "test-role2"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_role" "role3" {
name = "test-role3"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
name = "test-role3"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_group" "group" {
name = "test-group"

View File

@ -2,6 +2,7 @@ package aws
import (
"fmt"
"net/url"
"regexp"
"time"
@ -20,6 +21,10 @@ func resourceAwsIamRole() *schema.Resource {
Update: resourceAwsIamRoleUpdate,
Delete: resourceAwsIamRoleDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"arn": &schema.Schema{
Type: schema.TypeString,
@ -174,6 +179,10 @@ func resourceAwsIamRoleReadResult(d *schema.ResourceData, role *iam.Role) error
if err := d.Set("unique_id", role.RoleId); err != nil {
return err
}
policy, _ := url.QueryUnescape(*role.AssumeRolePolicyDocument)
if err := d.Set("assume_role_policy", aws.String(policy)); err != nil {
return err
}
return nil
}

View File

@ -90,22 +90,8 @@ func testAccCheckAWSRolePolicyAttachmentAttributes(policies []string, out *iam.L
const testAccAWSRolePolicyAttachConfig = `
resource "aws_iam_role" "role" {
name = "test-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
name = "test-role"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_policy" "policy" {
@ -135,22 +121,8 @@ resource "aws_iam_role_policy_attachment" "test-attach" {
const testAccAWSRolePolicyAttachConfigUpdate = `
resource "aws_iam_role" "role" {
name = "test-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
name = "test-role"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_policy" "policy" {

View File

@ -113,15 +113,15 @@ func testAccCheckIAMRolePolicy(
func testAccIAMRolePolicyConfig(role, policy1 string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "role" {
name = "tf_test_role_%s"
path = "/"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Effect\":\"Allow\",\"Sid\":\"\"}]}"
name = "tf_test_role_%s"
path = "/"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_role_policy" "foo" {
name = "tf_test_policy_%s"
role = "${aws_iam_role.role.name}"
policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}"
name = "tf_test_policy_%s"
role = "${aws_iam_role.role.name}"
policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}"
}
`, role, policy1)
}
@ -129,21 +129,21 @@ resource "aws_iam_role_policy" "foo" {
func testAccIAMRolePolicyConfigUpdate(role, policy1, policy2 string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "role" {
name = "tf_test_role_%s"
path = "/"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Effect\":\"Allow\",\"Sid\":\"\"}]}"
name = "tf_test_role_%s"
path = "/"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_role_policy" "foo" {
name = "tf_test_policy_%s"
role = "${aws_iam_role.role.name}"
policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}"
name = "tf_test_policy_%s"
role = "${aws_iam_role.role.name}"
policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}"
}
resource "aws_iam_role_policy" "bar" {
name = "tf_test_policy_2_%s"
role = "${aws_iam_role.role.name}"
policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}"
name = "tf_test_policy_2_%s"
role = "${aws_iam_role.role.name}"
policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}"
}
`, role, policy1, policy2)
}

View File

@ -165,39 +165,24 @@ func testAccCheckAWSRoleAttributes(role *iam.GetRoleOutput) resource.TestCheckFu
const testAccAWSRoleConfig = `
resource "aws_iam_role" "role" {
name = "test-role"
path = "/"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ec2.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}"
name = "test-role"
path = "/"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
`
const testAccAWSRolePrefixNameConfig = `
resource "aws_iam_role" "role" {
name_prefix = "test-role-"
path = "/"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":[\"ec2.amazonaws.com\"]},\"Action\":[\"sts:AssumeRole\"]}]}"
name_prefix = "test-role-"
path = "/"
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
`
const testAccAWSRolePre = `
resource "aws_iam_role" "role_update_test" {
name = "tf_old_name"
path = "/test/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_role_policy" "role_update_test" {
@ -232,21 +217,7 @@ const testAccAWSRolePost = `
resource "aws_iam_role" "role_update_test" {
name = "tf_new_name"
path = "/test/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
assume_role_policy = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}"
}
resource "aws_iam_role_policy" "role_update_test" {

View File

@ -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]
}

View File

@ -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 = <<EOF
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAv4sWWwIkbm3FmnjK676VCe5SuAFdEb6KtCpxNeVX3UGnmGmZ
EjKGcYK1bHEMFQvGuHhY3dVX/SMPMpP3vqguA7LeGPnoJ2p/4mVnWqAw3lscHeab
dpVaeuewtsGx/BlIRuTcqG39wTWcra2g1Vb6vOdLeb56hXdmf8NQ3AaVmvjaEY2z
oROfaNRFjvwROpGZbjvG+N/zp4iju9QsclpjrHJqs+qQvwNlrM6a9jTqFQa8bAYG
fLuC6B9eoMb4jydw8GwYkZICYeZVXyjREIH1SNj5O9IxBGkQOhWW/Ksa6CWwPy2j
/j7tRyy4MaVFg3qOYg4LfuY6L+PBF9YvVhRrgwIDAQABAoIBAFqJ4h1Om+3e0WK8
6h4YzdYN4ue7LUTv7hxPW4gASlH5cMDoWURywX3yLNN/dBiWom4b5NWmvJqY8dwU
eSyTznxNFhJ0PjozaxOWnw4FXlQceOPhV2bsHgKudadNU1Y4lSN9lpe+tg2Xy+GE
ituM66RTKCf502w3DioiJpx6OEkxuhrnsQAWNcGB0MnTukm2f+629V+04R5MT5V1
nY+5Phx2BpHgYzWBKh6Px1puu7xFv5SMQda1ndlPIKb4cNp0yYn+1lHNjbOE7QL/
oEpWgrauS5Zk/APK33v/p3wVYHrKocIFHlPiCW0uIJJLsOZDY8pQXpTlc+/xGLLy
WBu4boECgYEA6xO+1UNh6ndJ3xGuNippH+ucTi/uq1+0tG1bd63v+75tn5l4LyY2
CWHRaWVlVn+WnDslkQTJzFD68X+9M7Cc4oP6WnhTyPamG7HlGv5JxfFHTC9GOKmz
sSc624BDmqYJ7Xzyhe5kc3iHzqG/L72ZF1aijZdrodQMSY1634UX6aECgYEA0Jdr
cBPSN+mgmEY6ogN5h7sO5uNV3TQQtW2IslfWZn6JhSRF4Rf7IReng48CMy9ZhFBy
Q7H2I1pDGjEC9gQHhgVfm+FyMSVqXfCHEW/97pvvu9ougHA0MhPep1twzTGrqg+K
f3PLW8hVkGyCrTfWgbDlPsHgsocA/wTaQOheaqMCgYBat5z+WemQfQZh8kXDm2xE
KD2Cota9BcsLkeQpdFNXWC6f167cqydRSZFx1fJchhJOKjkeFLX3hgzBY6VVLEPu
2jWj8imLNTv3Fhiu6RD5NVppWRkFRuAUbmo1SPNN2+Oa5YwGCXB0a0Alip/oQYex
zPogIB4mLlmrjNCtL4SB4QKBgCEHKMrZSJrz0irqS9RlanPUaZqjenAJE3A2xMNA
Z0FZXdsIEEyA6JGn1i1dkoKaR7lMp5sSbZ/RZfiatBZSMwLEjQv4mYUwoHP5Ztma
+wEyDbaX6G8L1Sfsv3+OWgETkVPfHBXsNtH0mZ/BnrtgsQVeBh52wmZiPAUlNo26
fWCzAoGBAJOjqovLelLWzyQGqPFx/MwuI56UFXd1CmFlCIvF2WxCFmk3tlExoCN1
HqSpt92vsgYgV7+lAb4U7Uy/v012gwiU1LK+vyAE9geo3pTjG73BNzG4H547xtbY
dg+Sd4Wjm89UQoUUoiIcstY7FPbqfBtYKfh4RYHAHV2BwDFqzZCM
-----END RSA PRIVATE KEY-----
EOF
}
`, rInt, fName)
}

View File

@ -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{})

View File

@ -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
}

View File

@ -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)
}

View File

@ -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" {

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDBjCCAe4CCQCGWwBmOiHQdTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE2MDYyMTE2MzM0MVoXDTE3MDYyMTE2MzM0MVowRTELMAkG
A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AL+LFlsCJG5txZp4yuu+lQnuUrgBXRG+irQqcTXlV91Bp5hpmRIyhnGCtWxxDBUL
xrh4WN3VV/0jDzKT976oLgOy3hj56Cdqf+JlZ1qgMN5bHB3mm3aVWnrnsLbBsfwZ
SEbk3Kht/cE1nK2toNVW+rznS3m+eoV3Zn/DUNwGlZr42hGNs6ETn2jURY78ETqR
mW47xvjf86eIo7vULHJaY6xyarPqkL8DZazOmvY06hUGvGwGBny7gugfXqDG+I8n
cPBsGJGSAmHmVV8o0RCB9UjY+TvSMQRpEDoVlvyrGuglsD8to/4+7UcsuDGlRYN6
jmIOC37mOi/jwRfWL1YUa4MCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAPDxTH0oQ
JjKXoJgkmQxurB81RfnK/NrswJVzWbOv6ejcbhwh+/ZgJTMc15BrYcxU6vUW1V/i
Z7APU0qJ0icECACML+a2fRI7YdLCTiPIOmY66HY8MZHAn3dGjU5TeiUflC0n0zkP
mxKJe43kcYLNDItbfvUDo/GoxTXrC3EFVZyU0RhFzoVJdODlTHXMVFCzcbQEBrBJ
xKdShCEc8nFMneZcGFeEU488ntZoWzzms8/QpYrKa5S0Sd7umEU2Kwu4HTkvUFg/
CqDUFjhydXxYRsxXBBrEiLOE5BdtJR1sH/QHxIJe23C9iHI2nS1NbLziNEApLwC4
GnSud83VUo9G9w==
-----END CERTIFICATE-----

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDBjCCAe4CCQCGWwBmOiHQdTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE2MDYyMTE2MzM0MVoXDTE3MDYyMTE2MzM0MVowRTELMAkG
A1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AL+LFlsCJG5txZp4yuu+lQnuUrgBXRG+irQqcTXlV91Bp5hpmRIyhnGCtWxxDBUL
xrh4WN3VV/0jDzKT976oLgOy3hj56Cdqf+JlZ1qgMN5bHB3mm3aVWnrnsLbBsfwZ
SEbk3Kht/cE1nK2toNVW+rznS3m+eoV3Zn/DUNwGlZr42hGNs6ETn2jURY78ETqR
mW47xvjf86eIo7vULHJaY6xyarPqkL8DZazOmvY06hUGvGwGBny7gugfXqDG+I8n
cPBsGJGSAmHmVV8o0RCB9UjY+TvSMQRpEDoVlvyrGuglsD8to/4+7UcsuDGlRYN6
jmIOC37mOi/jwRfWL1YUa4MCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAPDxTH0oQ
JjKXoJgkmQxurB81RfnK/NrswJVzWbOv6ejcbhwh+/ZgJTMc15BrYcxU6vUW1V/i
Z7APU0qJ0icECACML+a2fRI7YdLCTiPIOmY66HY8MZHAn3dGjU5TeiUflC0n0zkP
mxKJe43kcYLNDItbfvUDo/GoxTXrC3EFVZyU0RhFzoVJdODlTHXMVFCzcbQEBrBJ
xKdShCEc8nFMneZcGFeEU488ntZoWzzms8/QpYrKa5S0Sd7umEU2Kwu4HTkvUFg/
CqDUFjhydXxYRsxXBBrEiLOE5BdtJR1sH/QHxIJe23C9iHI2nS1NbLziNEApLwC4
GnSud83VUo9G9w==
-----END CERTIFICATE-----

View File

@ -0,0 +1,70 @@
package fastly
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"sort"
"strconv"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/terraform/helper/hashcode"
"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{
"cidr_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")
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)
}
d.SetId(strconv.Itoa(hashcode.String(string(data))))
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("cidr_blocks", result.Addresses); err != nil {
return fmt.Errorf("Error setting ip ranges: %s", err)
}
return nil
}

View File

@ -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" {
}
`

View File

@ -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(),
},

View File

@ -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{

View File

@ -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

View File

@ -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

View File

@ -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.
* `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.

View File

@ -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

View File

@ -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

View File

@ -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}"`

View File

@ -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

View File

@ -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
@ -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

View File

@ -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" {

View File

@ -25,6 +25,9 @@
<li<%= sidebar_current("docs-aws-datasource-iam-policy-document") %>>
<a href="/docs/providers/aws/d/iam_policy_document.html">aws_iam_policy_document</a>
</li>
<li<%= sidebar_current("docs-aws-datasource-ip_ranges") %>>
<a href="/docs/providers/aws/d/ip_ranges.html">aws_ip_ranges</a>
</li>
<li<%= sidebar_current("docs-aws-datasource-s3-bucket-object") %>>
<a href="/docs/providers/aws/d/s3_bucket_object.html">aws_s3_bucket_object</a>
</li>
@ -567,6 +570,10 @@
<a href="#">OpsWorks Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-aws-resource-opsworks-application") %>>
<a href="/docs/providers/aws/r/opsworks_application.html">aws_opsworks_application</a>
</li>
<li<%= sidebar_current("docs-aws-resource-opsworks-custom-layer") %>>
<a href="/docs/providers/aws/r/opsworks_custom_layer.html">aws_opsworks_custom_layer</a>
</li>
@ -599,6 +606,10 @@
<a href="/docs/providers/aws/r/opsworks_nodejs_app_layer.html">aws_opsworks_nodejs_app_layer</a>
</li>
<li<%= sidebar_current("docs-aws-resource-opsworks-permission") %>>
<a href="/docs/providers/aws/r/opsworks_permission.html">aws_opsworks_permission</a>
</li>
<li<%= sidebar_current("docs-aws-resource-opsworks-php-app-layer") %>>
<a href="/docs/providers/aws/r/opsworks_php_app_layer.html">aws_opsworks_php_app_layer</a>
</li>
@ -615,8 +626,8 @@
<a href="/docs/providers/aws/r/opsworks_static_web_layer.html">aws_opsworks_static_web_layer</a>
</li>
<li<%= sidebar_current("docs-aws-resource-opsworks-application") %>>
<a href="/docs/providers/aws/r/opsworks_application.html">aws_opsworks_application</a>
<li<%= sidebar_current("docs-aws-resource-opsworks-user-profile") %>>
<a href="/docs/providers/aws/r/opsworks_user_profile.html">aws_opsworks_user_profile</a>
</li>
</ul>

View File

@ -10,6 +10,15 @@
<a href="/docs/providers/fastly/index.html">Fastly Provider</a>
</li>
<li<%= sidebar_current(/^docs-fastly-datasource/) %>>
<a href="#">Data Sources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-fastly-datasource-ip_ranges") %>>
<a href="/docs/providers/fastly/d/ip_ranges.html">fastly_ip_ranges</a>
</li>
</ul>
</li>
<li<%= sidebar_current(/^docs-fastly-resource/) %>>
<a href="#">Resources</a>