diff --git a/builtin/providers/aws/resource_aws_iam_instance_profile.go b/builtin/providers/aws/resource_aws_iam_instance_profile.go index 95c8a55e09ba..c84459e00d73 100644 --- a/builtin/providers/aws/resource_aws_iam_instance_profile.go +++ b/builtin/providers/aws/resource_aws_iam_instance_profile.go @@ -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), 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 985729aad3ff..93001184bc14 100644 --- a/builtin/providers/aws/resource_aws_iam_instance_profile_test.go +++ b/builtin/providers/aws/resource_aws_iam_instance_profile_test.go @@ -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}"] +} +` diff --git a/builtin/providers/aws/resource_aws_iam_role.go b/builtin/providers/aws/resource_aws_iam_role.go index b746c6af2cce..effb95c36daa 100644 --- a/builtin/providers/aws/resource_aws_iam_role.go +++ b/builtin/providers/aws/resource_aws_iam_role.go @@ -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 diff --git a/builtin/providers/aws/resource_aws_iam_role_test.go b/builtin/providers/aws/resource_aws_iam_role_test.go index e61d49b2af38..ee3945e37968 100644 --- a/builtin/providers/aws/resource_aws_iam_role_test.go +++ b/builtin/providers/aws/resource_aws_iam_role_test.go @@ -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" diff --git a/website/source/docs/providers/aws/r/iam_instance_profile.html.markdown b/website/source/docs/providers/aws/r/iam_instance_profile.html.markdown index aaed8cf534cd..05ec3022f39e 100644 --- a/website/source/docs/providers/aws/r/iam_instance_profile.html.markdown +++ b/website/source/docs/providers/aws/r/iam_instance_profile.html.markdown @@ -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. diff --git a/website/source/docs/providers/aws/r/iam_role.html.markdown b/website/source/docs/providers/aws/r/iam_role.html.markdown index 7a5d0df171f6..c9862dd0724b 100644 --- a/website/source/docs/providers/aws/r/iam_role.html.markdown +++ b/website/source/docs/providers/aws/r/iam_role.html.markdown @@ -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.