Allow `name_prefix` to be used with various IAM resources (#12658)

Adds the `name_prefix` to `aws_iam_group_policy`, `aws_iam_role_policy` and `aws_iam_user_policy`.
This commit is contained in:
Joshua Spence 2017-03-18 03:48:42 +11:00 committed by Paul Stack
parent e8dd668063
commit b823033514
14 changed files with 419 additions and 29 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
@ -27,8 +28,15 @@ func resourceAwsIamGroupPolicy() *schema.Resource {
Required: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
},
"name_prefix": &schema.Schema{
Type: schema.TypeString,
Required: true,
Optional: true,
ForceNew: true,
},
"group": &schema.Schema{
@ -45,10 +53,19 @@ func resourceAwsIamGroupPolicyPut(d *schema.ResourceData, meta interface{}) erro
request := &iam.PutGroupPolicyInput{
GroupName: aws.String(d.Get("group").(string)),
PolicyName: aws.String(d.Get("name").(string)),
PolicyDocument: aws.String(d.Get("policy").(string)),
}
var policyName string
if v, ok := d.GetOk("name"); ok {
policyName = v.(string)
} else if v, ok := d.GetOk("name_prefix"); ok {
policyName = resource.PrefixedUniqueId(v.(string))
} else {
policyName = resource.UniqueId()
}
request.PolicyName = aws.String(policyName)
if _, err := iamconn.PutGroupPolicy(request); err != nil {
return fmt.Errorf("Error putting IAM group policy %s: %s", *request.PolicyName, err)
}

View File

@ -39,6 +39,46 @@ func TestAccAWSIAMGroupPolicy_basic(t *testing.T) {
})
}
func TestAccAWSIAMGroupPolicy_namePrefix(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_iam_group_policy.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckIAMGroupPolicyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccIAMGroupPolicyConfig_namePrefix,
Check: resource.ComposeTestCheckFunc(
testAccCheckIAMGroupPolicy(
"aws_iam_group.test",
"aws_iam_group_policy.test",
),
),
},
},
})
}
func TestAccAWSIAMGroupPolicy_generatedName(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_iam_group_policy.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckIAMGroupPolicyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccIAMGroupPolicyConfig_generatedName,
Check: resource.ComposeTestCheckFunc(
testAccCheckIAMGroupPolicy(
"aws_iam_group.test",
"aws_iam_group_policy.test",
),
),
},
},
})
}
func testAccCheckIAMGroupPolicyDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).iamconn
@ -124,6 +164,49 @@ EOF
}
`
const testAccIAMGroupPolicyConfig_namePrefix = `
resource "aws_iam_group" "test" {
name = "test_group"
path = "/"
}
resource "aws_iam_group_policy" "test" {
name_prefix = "test-"
group = "${aws_iam_group.test.name}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
}
EOF
}
`
const testAccIAMGroupPolicyConfig_generatedName = `
resource "aws_iam_group" "test" {
name = "test_group"
path = "/"
}
resource "aws_iam_group_policy" "test" {
group = "${aws_iam_group.test.name}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
}
EOF
}
`
const testAccIAMGroupPolicyConfigUpdate = `
resource "aws_iam_group" "group" {
name = "test_group"

View File

@ -3,13 +3,13 @@ package aws
import (
"fmt"
"net/url"
"regexp"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
@ -31,22 +31,18 @@ func resourceAwsIamRolePolicy() *schema.Resource {
Required: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
// https://github.com/boto/botocore/blob/2485f5c/botocore/data/iam/2010-05-08/service-2.json#L8291-L8296
value := v.(string)
if len(value) > 128 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 128 characters", k))
}
if !regexp.MustCompile("^[\\w+=,.@-]+$").MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q must match [\\w+=,.@-]", k))
}
return
},
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
ValidateFunc: validateIamRolePolicyName,
},
"name_prefix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validateIamRolePolicyNamePrefix,
},
"role": &schema.Schema{
Type: schema.TypeString,
@ -62,10 +58,19 @@ func resourceAwsIamRolePolicyPut(d *schema.ResourceData, meta interface{}) error
request := &iam.PutRolePolicyInput{
RoleName: aws.String(d.Get("role").(string)),
PolicyName: aws.String(d.Get("name").(string)),
PolicyDocument: aws.String(d.Get("policy").(string)),
}
var policyName string
if v, ok := d.GetOk("name"); ok {
policyName = v.(string)
} else if v, ok := d.GetOk("name_prefix"); ok {
policyName = resource.PrefixedUniqueId(v.(string))
} else {
policyName = resource.UniqueId()
}
request.PolicyName = aws.String(policyName)
if _, err := iamconn.PutRolePolicy(request); err != nil {
return fmt.Errorf("Error putting IAM role policy %s: %s", *request.PolicyName, err)
}

View File

@ -44,6 +44,50 @@ func TestAccAWSIAMRolePolicy_basic(t *testing.T) {
})
}
func TestAccAWSIAMRolePolicy_namePrefix(t *testing.T) {
role := acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_iam_role_policy.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckIAMRolePolicyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccIAMRolePolicyConfig_namePrefix(role),
Check: resource.ComposeTestCheckFunc(
testAccCheckIAMRolePolicy(
"aws_iam_role.test",
"aws_iam_role_policy.test",
),
),
},
},
})
}
func TestAccAWSIAMRolePolicy_generatedName(t *testing.T) {
role := acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_iam_role_policy.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckIAMRolePolicyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccIAMRolePolicyConfig_generatedName(role),
Check: resource.ComposeTestCheckFunc(
testAccCheckIAMRolePolicy(
"aws_iam_role.test",
"aws_iam_role_policy.test",
),
),
},
},
})
}
func testAccCheckIAMRolePolicyDestroy(s *terraform.State) error {
iamconn := testAccProvider.Meta().(*AWSClient).iamconn
@ -154,6 +198,83 @@ EOF
`, role, policy1)
}
func testAccIAMRolePolicyConfig_namePrefix(role string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "test" {
name = "tf_test_role_%s"
path = "/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_role_policy" "test" {
name_prefix = "tf_test_policy_"
role = "${aws_iam_role.test.name}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
}
EOF
}
`, role)
}
func testAccIAMRolePolicyConfig_generatedName(role string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "test" {
name = "tf_test_role_%s"
path = "/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_role_policy" "test" {
role = "${aws_iam_role.test.name}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
}
EOF
}
`, role)
}
func testAccIAMRolePolicyConfigUpdate(role, policy1, policy2 string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "role" {

View File

@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
@ -27,8 +28,15 @@ func resourceAwsIamUserPolicy() *schema.Resource {
Required: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
},
"name_prefix": &schema.Schema{
Type: schema.TypeString,
Required: true,
Optional: true,
ForceNew: true,
},
"user": &schema.Schema{
@ -45,10 +53,19 @@ func resourceAwsIamUserPolicyPut(d *schema.ResourceData, meta interface{}) error
request := &iam.PutUserPolicyInput{
UserName: aws.String(d.Get("user").(string)),
PolicyName: aws.String(d.Get("name").(string)),
PolicyDocument: aws.String(d.Get("policy").(string)),
}
var policyName string
if v, ok := d.GetOk("name"); ok {
policyName = v.(string)
} else if v, ok := d.GetOk("name_prefix"); ok {
policyName = resource.PrefixedUniqueId(v.(string))
} else {
policyName = resource.UniqueId()
}
request.PolicyName = aws.String(policyName)
if _, err := iamconn.PutUserPolicy(request); err != nil {
return fmt.Errorf("Error putting IAM user policy %s: %s", *request.PolicyName, err)
}

View File

@ -39,6 +39,46 @@ func TestAccAWSIAMUserPolicy_basic(t *testing.T) {
})
}
func TestAccAWSIAMUserPolicy_namePrefix(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_iam_user_policy.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckIAMUserPolicyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccIAMUserPolicyConfig_namePrefix,
Check: resource.ComposeTestCheckFunc(
testAccCheckIAMUserPolicy(
"aws_iam_user.test",
"aws_iam_user_policy.test",
),
),
},
},
})
}
func TestAccAWSIAMUserPolicy_generatedName(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_iam_user_policy.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckIAMUserPolicyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccIAMUserPolicyConfig_generatedName,
Check: resource.ComposeTestCheckFunc(
testAccCheckIAMUserPolicy(
"aws_iam_user.test",
"aws_iam_user_policy.test",
),
),
},
},
})
}
func testAccCheckIAMUserPolicyDestroy(s *terraform.State) error {
iamconn := testAccProvider.Meta().(*AWSClient).iamconn
@ -118,6 +158,31 @@ resource "aws_iam_user_policy" "foo" {
}
`
const testAccIAMUserPolicyConfig_namePrefix = `
resource "aws_iam_user" "test" {
name = "test_user"
path = "/"
}
resource "aws_iam_user_policy" "test" {
name_prefix = "test-"
user = "${aws_iam_user.test.name}"
policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}"
}
`
const testAccIAMUserPolicyConfig_generatedName = `
resource "aws_iam_user" "test" {
name = "test_user"
path = "/"
}
resource "aws_iam_user_policy" "test" {
user = "${aws_iam_user.test.name}"
policy = "{\"Version\":\"2012-10-17\",\"Statement\":{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}}"
}
`
const testAccIAMUserPolicyConfigUpdate = `
resource "aws_iam_user" "user" {
name = "test_user"

View File

@ -949,3 +949,28 @@ func validateAccountAlias(v interface{}, k string) (ws []string, es []error) {
return
}
func validateIamRolePolicyName(v interface{}, k string) (ws []string, errors []error) {
// https://github.com/boto/botocore/blob/2485f5c/botocore/data/iam/2010-05-08/service-2.json#L8291-L8296
value := v.(string)
if len(value) > 128 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 128 characters", k))
}
if !regexp.MustCompile("^[\\w+=,.@-]+$").MatchString(value) {
errors = append(errors, fmt.Errorf("%q must match [\\w+=,.@-]", k))
}
return
}
func validateIamRolePolicyNamePrefix(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if len(value) > 100 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 100 characters", k))
}
if !regexp.MustCompile("^[\\w+=,.@-]+$").MatchString(value) {
errors = append(errors, fmt.Errorf("%q must match [\\w+=,.@-]", k))
}
return
}

View File

@ -1579,3 +1579,53 @@ func TestValidateAccountAlias(t *testing.T) {
}
}
}
func TestValidateIamRoleProfileName(t *testing.T) {
validNames := []string{
"tf-test-role-profile-1",
}
for _, s := range validNames {
_, errors := validateIamRolePolicyName(s, "name")
if len(errors) > 0 {
t.Fatalf("%q should be a valid IAM role policy name: %v", s, errors)
}
}
invalidNames := []string{
"invalid#name",
"this-is-a-very-long-role-policy-name-this-is-a-very-long-role-policy-name-this-is-a-very-long-role-policy-name-this-is-a-very-long",
}
for _, s := range invalidNames {
_, errors := validateIamRolePolicyName(s, "name")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid IAM role policy name: %v", s, errors)
}
}
}
func TestValidateIamRoleProfileNamePrefix(t *testing.T) {
validNamePrefixes := []string{
"tf-test-role-profile-",
}
for _, s := range validNamePrefixes {
_, errors := validateIamRolePolicyNamePrefix(s, "name_prefix")
if len(errors) > 0 {
t.Fatalf("%q should be a valid IAM role policy name prefix: %v", s, errors)
}
}
invalidNamePrefixes := []string{
"invalid#name_prefix",
"this-is-a-very-long-role-policy-name-prefix-this-is-a-very-long-role-policy-name-prefix-this-is-a-very-",
}
for _, s := range invalidNamePrefixes {
_, errors := validateIamRolePolicyNamePrefix(s, "name_prefix")
if len(errors) == 0 {
t.Fatalf("%q should not be a valid IAM role policy name prefix: %v", s, errors)
}
}
}

View File

@ -45,7 +45,10 @@ The following arguments are supported:
* `policy` - (Required) The policy document. This is a JSON formatted string.
The heredoc syntax or `file` function is helpful here.
* `name` - (Required) Name of the policy.
* `name` - (Optional) The name of the policy. If omitted, Terraform will
assign a random, unique name.
* `name_prefix` - (Optional) Creates a unique name beginning with the specified
prefix. Conflicts with `name`.
* `group` - (Required) The IAM group to attach to the policy.
## Attributes Reference

View File

@ -44,7 +44,7 @@ EOF
The following arguments are supported:
* `name` - (Optional, Forces new resource) The profile's name.
* `name` - (Optional, Forces new resource) The profile's name. If omitted, Terraform will assign a random, unique name.
* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`.
* `path` - (Optional, default "/") Path in which to create the profile.
* `roles` - (Required) A list of role names to include in the profile. The current default is 1. If you see an error message similar to `Cannot exceed quota for InstanceSessionsPerInstanceProfile: 1`, then you must contact AWS support and ask for a limit increase.

View File

@ -38,7 +38,7 @@ EOF
The following arguments are supported:
* `description` - (Optional) Description of the IAM policy.
* `name` - (Optional, Forces new resource) The name of the policy.
* `name` - (Optional, Forces new resource) The name of the policy. If omitted, Terraform will assign a random, unique name.
* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`.
* `path` - (Optional, default "/") Path in which to create the policy.
See [IAM Identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html) for more information.
@ -64,4 +64,4 @@ IAM Policies can be imported using the `arn`, e.g.
```
$ terraform import aws_iam_policy.administrator arn:aws:iam::123456789012:policy/UsersManageOwnCredentials
```
```

View File

@ -38,7 +38,7 @@ EOF
The following arguments are supported:
* `name` - (Optional, Forces new resource) The name of the role.
* `name` - (Optional, Forces new resource) The name of the role. If omitted, Terraform will assign a random, unique name.
* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`.
* `assume_role_policy` - (Required) The policy that grants an entity permission to assume the role.

View File

@ -58,7 +58,10 @@ EOF
The following arguments are supported:
* `name` - (Required) The name of the role policy.
* `name` - (Optional) The name of the role policy. If omitted, Terraform will
assign a random, unique name.
* `name_prefix` - (Optional) Creates a unique name beginning with the specified
prefix. Conflicts with `name`.
* `policy` - (Required) The policy document. This is a JSON formatted string.
The heredoc syntax or `file` function is helpful here.
* `role` - (Required) The IAM role to attach to the policy.

View File

@ -49,7 +49,8 @@ The following arguments are supported:
* `policy` - (Required) The policy document. This is a JSON formatted string.
The heredoc syntax or `file` function is helpful here.
* `name` - (Required) Name of the policy.
* `name` - (Optional) The name of the policy. If omitted, Terraform will assign a random, unique name.
* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`.
* `user` - (Required) IAM user to which to attach this policy.
## Attributes Reference