Add `name_prefix` to `aws_iam_instance_profile` and `aws_iam_role` (#6939)

This commit is contained in:
Joshua Spence 2016-06-05 09:46:27 +10:00 committed by Paul Stack
parent e44727e086
commit d3030e1e0f
6 changed files with 242 additions and 10 deletions

View File

@ -8,6 +8,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"
)
@ -23,18 +24,23 @@ func resourceAwsIamInstanceProfile() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"create_date": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"unique_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
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#L8196-L8201
value := v.(string)
@ -49,12 +55,33 @@ func resourceAwsIamInstanceProfile() *schema.Resource {
return
},
},
"name_prefix": &schema.Schema{
Type: schema.TypeString,
Optional: 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#L8196-L8201
value := v.(string)
if len(value) > 64 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 64 characters, name is limited to 128", k))
}
if !regexp.MustCompile("^[\\w+=,.@-]+$").MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q must match [\\w+=,.@-]", k))
}
return
},
},
"path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "/",
ForceNew: true,
},
"roles": &schema.Schema{
Type: schema.TypeSet,
Required: true,
@ -67,7 +94,15 @@ func resourceAwsIamInstanceProfile() *schema.Resource {
func resourceAwsIamInstanceProfileCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
name := d.Get("name").(string)
var name string
if v, ok := d.GetOk("name"); ok {
name = v.(string)
} else if v, ok := d.GetOk("name_prefix"); ok {
name = resource.PrefixedUniqueId(v.(string))
} else {
name = resource.UniqueId()
}
request := &iam.CreateInstanceProfileInput{
InstanceProfileName: aws.String(name),

View File

@ -1,9 +1,15 @@
package aws
import (
"fmt"
"strings"
"testing"
"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/terraform"
)
func TestAccAWSIAMInstanceProfile_basic(t *testing.T) {
@ -18,6 +24,100 @@ func TestAccAWSIAMInstanceProfile_basic(t *testing.T) {
})
}
func TestAccAWSIAMInstanceProfile_namePrefix(t *testing.T) {
var conf iam.GetInstanceProfileOutput
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_iam_instance_profile.test",
IDRefreshIgnore: []string{"name_prefix"},
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSInstanceProfileDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSInstanceProfilePrefixNameConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSInstanceProfileExists("aws_iam_instance_profile.test", &conf),
testAccCheckAWSInstanceProfileGeneratedNamePrefix(
"aws_iam_instance_profile.test", "test-"),
),
},
},
})
}
func testAccCheckAWSInstanceProfileGeneratedNamePrefix(resource, prefix string) resource.TestCheckFunc {
return func(s *terraform.State) error {
r, ok := s.RootModule().Resources[resource]
if !ok {
return fmt.Errorf("Resource not found")
}
name, ok := r.Primary.Attributes["name"]
if !ok {
return fmt.Errorf("Name attr not found: %#v", r.Primary.Attributes)
}
if !strings.HasPrefix(name, prefix) {
return fmt.Errorf("Name: %q, does not have prefix: %q", name, prefix)
}
return nil
}
}
func testAccCheckAWSInstanceProfileDestroy(s *terraform.State) error {
iamconn := testAccProvider.Meta().(*AWSClient).iamconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_iam_instance_profile" {
continue
}
// Try to get role
_, err := iamconn.GetInstanceProfile(&iam.GetInstanceProfileInput{
InstanceProfileName: aws.String(rs.Primary.ID),
})
if err == nil {
return fmt.Errorf("still exist.")
}
// Verify the error is what we want
ec2err, ok := err.(awserr.Error)
if !ok {
return err
}
if ec2err.Code() != "NoSuchEntity" {
return err
}
}
return nil
}
func testAccCheckAWSInstanceProfileExists(n string, res *iam.GetInstanceProfileOutput) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No Instance Profile name is set")
}
iamconn := testAccProvider.Meta().(*AWSClient).iamconn
resp, err := iamconn.GetInstanceProfile(&iam.GetInstanceProfileInput{
InstanceProfileName: aws.String(rs.Primary.ID),
})
if err != nil {
return err
}
*res = *resp
return nil
}
}
const testAccAwsIamInstanceProfileConfig = `
resource "aws_iam_role" "test" {
name = "test"
@ -29,3 +129,15 @@ resource "aws_iam_instance_profile" "test" {
roles = ["${aws_iam_role.test.name}"]
}
`
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\"]}]}"
}
resource "aws_iam_instance_profile" "test" {
name_prefix = "test-"
roles = ["${aws_iam_role.test.name}"]
}
`

View File

@ -8,6 +8,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"
)
@ -23,14 +24,18 @@ func resourceAwsIamRole() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"unique_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
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#L8329-L8334
value := v.(string)
@ -45,12 +50,33 @@ func resourceAwsIamRole() *schema.Resource {
return
},
},
"name_prefix": &schema.Schema{
Type: schema.TypeString,
Optional: 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#L8329-L8334
value := v.(string)
if len(value) > 32 {
errors = append(errors, fmt.Errorf(
"%q cannot be longer than 32 characters, name is limited to 64", k))
}
if !regexp.MustCompile("^[\\w+=,.@-]*$").MatchString(value) {
errors = append(errors, fmt.Errorf(
"%q must match [\\w+=,.@-]", k))
}
return
},
},
"path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "/",
ForceNew: true,
},
"assume_role_policy": &schema.Schema{
Type: schema.TypeString,
Required: true,
@ -61,7 +87,15 @@ func resourceAwsIamRole() *schema.Resource {
func resourceAwsIamRoleCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
name := d.Get("name").(string)
var name string
if v, ok := d.GetOk("name"); ok {
name = v.(string)
} else if v, ok := d.GetOk("name_prefix"); ok {
name = resource.PrefixedUniqueId(v.(string))
} else {
name = resource.UniqueId()
}
request := &iam.CreateRoleInput{
Path: aws.String(d.Get("path").(string)),
@ -93,6 +127,7 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error {
}
return resourceAwsIamRoleReadResult(d, getResp.Role)
}
func resourceAwsIamRoleUpdate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn

View File

@ -2,6 +2,7 @@ package aws
import (
"fmt"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
@ -30,6 +31,28 @@ func TestAccAWSRole_basic(t *testing.T) {
})
}
func TestAccAWSRole_namePrefix(t *testing.T) {
var conf iam.GetRoleOutput
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "aws_iam_role.role",
IDRefreshIgnore: []string{"name_prefix"},
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSRoleDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSRolePrefixNameConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSRoleExists("aws_iam_role.role", &conf),
testAccCheckAWSRoleGeneratedNamePrefix(
"aws_iam_role.role", "test-role-"),
),
},
},
})
}
func TestAccAWSRole_testNameChange(t *testing.T) {
var conf iam.GetRoleOutput
@ -110,6 +133,23 @@ func testAccCheckAWSRoleExists(n string, res *iam.GetRoleOutput) resource.TestCh
}
}
func testAccCheckAWSRoleGeneratedNamePrefix(resource, prefix string) resource.TestCheckFunc {
return func(s *terraform.State) error {
r, ok := s.RootModule().Resources[resource]
if !ok {
return fmt.Errorf("Resource not found")
}
name, ok := r.Primary.Attributes["name"]
if !ok {
return fmt.Errorf("Name attr not found: %#v", r.Primary.Attributes)
}
if !strings.HasPrefix(name, prefix) {
return fmt.Errorf("Name: %q, does not have prefix: %q", name, prefix)
}
return nil
}
}
func testAccCheckAWSRoleAttributes(role *iam.GetRoleOutput) resource.TestCheckFunc {
return func(s *terraform.State) error {
if *role.Role.RoleName != "test-role" {
@ -131,6 +171,14 @@ resource "aws_iam_role" "role" {
}
`
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\"]}]}"
}
`
const testAccAWSRolePre = `
resource "aws_iam_role" "role_update_test" {
name = "tf_old_name"

View File

@ -41,7 +41,8 @@ EOF
The following arguments are supported:
* `name` - (Required) The profile's name.
* `name` - (Optional, Forces new resource) The profile's 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.

View File

@ -37,7 +37,8 @@ EOF
The following arguments are supported:
* `name` - (Required) The name of the role.
* `name` - (Optional, Forces new resource) The name of the role.
* `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.
* `path` - (Optional) The path to the role.
See [IAM Identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html) for more information.