diff --git a/builtin/providers/aws/resource_aws_instance.go b/builtin/providers/aws/resource_aws_instance.go index b32c72fe1..061c619ba 100644 --- a/builtin/providers/aws/resource_aws_instance.go +++ b/builtin/providers/aws/resource_aws_instance.go @@ -171,7 +171,6 @@ func resourceAwsInstance() *schema.Resource { "iam_instance_profile": { Type: schema.TypeString, - ForceNew: true, Optional: true, }, @@ -608,6 +607,66 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error { d.SetPartial("tags") } + if d.HasChange("iam_instance_profile") { + request := &ec2.DescribeIamInstanceProfileAssociationsInput{ + Filters: []*ec2.Filter{ + &ec2.Filter{ + Name: aws.String("instance-id"), + Values: []*string{aws.String(d.Id())}, + }, + }, + } + + resp, err := conn.DescribeIamInstanceProfileAssociations(request) + if err != nil { + return err + } + + // An Iam Instance Profile has been provided and is pending a change + // This means it is an association or a replacement to an association + if _, ok := d.GetOk("iam_instance_profile"); ok { + // Does not have an Iam Instance Profile associated with it, need to associate + if len(resp.IamInstanceProfileAssociations) == 0 { + _, err := conn.AssociateIamInstanceProfile(&ec2.AssociateIamInstanceProfileInput{ + InstanceId: aws.String(d.Id()), + IamInstanceProfile: &ec2.IamInstanceProfileSpecification{ + Name: aws.String(d.Get("iam_instance_profile").(string)), + }, + }) + if err != nil { + return err + } + + } else { + // Has an Iam Instance Profile associated with it, need to replace the association + associationId := resp.IamInstanceProfileAssociations[0].AssociationId + + _, err := conn.ReplaceIamInstanceProfileAssociation(&ec2.ReplaceIamInstanceProfileAssociationInput{ + AssociationId: associationId, + IamInstanceProfile: &ec2.IamInstanceProfileSpecification{ + Name: aws.String(d.Get("iam_instance_profile").(string)), + }, + }) + if err != nil { + return err + } + } + // An Iam Instance Profile has _not_ been provided but is pending a change. This means there is a pending removal + } else { + if len(resp.IamInstanceProfileAssociations) > 0 { + // Has an Iam Instance Profile associated with it, need to remove the association + associationId := resp.IamInstanceProfileAssociations[0].AssociationId + + _, err := conn.DisassociateIamInstanceProfile(&ec2.DisassociateIamInstanceProfileInput{ + AssociationId: associationId, + }) + if err != nil { + return err + } + } + } + } + if d.HasChange("source_dest_check") || d.IsNewResource() { // SourceDestCheck can only be set on VPC instances // AWS will return an error of InvalidParameterCombination if we attempt // to modify the source_dest_check of an instance in EC2 Classic diff --git a/builtin/providers/aws/resource_aws_instance_test.go b/builtin/providers/aws/resource_aws_instance_test.go index e6e2d62ef..aae53ecbd 100644 --- a/builtin/providers/aws/resource_aws_instance_test.go +++ b/builtin/providers/aws/resource_aws_instance_test.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" @@ -628,6 +629,43 @@ func TestAccAWSInstance_tags(t *testing.T) { }) } +func TestAccAWSInstance_instanceProfileChange(t *testing.T) { + var v ec2.Instance + rName := acctest.RandString(5) + + testCheckInstanceProfile := func() resource.TestCheckFunc { + return func(*terraform.State) error { + if v.IamInstanceProfile == nil { + return fmt.Errorf("Instance Profile is nil - we expected an InstanceProfile associated with the Instance") + } + + return nil + } + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_instance.foo", + Providers: testAccProviders, + CheckDestroy: testAccCheckInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccInstanceConfigWithoutInstanceProfile(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists("aws_instance.foo", &v), + ), + }, + { + Config: testAccInstanceConfigAttachInstanceProfile(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckInstanceExists("aws_instance.foo", &v), + testCheckInstanceProfile(), + ), + }, + }, + }) +} + func TestAccAWSInstance_privateIP(t *testing.T) { var v ec2.Instance @@ -1223,6 +1261,49 @@ resource "aws_instance" "foo" { } ` +func testAccInstanceConfigWithoutInstanceProfile(rName string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "test" { + name = "test-%s" + 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 = "test-%s" + roles = ["${aws_iam_role.test.name}"] +} + +resource "aws_instance" "foo" { + ami = "ami-4fccb37f" + instance_type = "m1.small" + tags { + bar = "baz" + } +}`, rName, rName) +} + +func testAccInstanceConfigAttachInstanceProfile(rName string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "test" { + name = "test-%s" + 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 = "test-%s" + roles = ["${aws_iam_role.test.name}"] +} + +resource "aws_instance" "foo" { + ami = "ami-4fccb37f" + instance_type = "m1.small" + iam_instance_profile = "${aws_iam_instance_profile.test.name}" + tags { + bar = "baz" + } +}`, rName, rName) +} + const testAccInstanceConfigPrivateIP = ` resource "aws_vpc" "foo" { cidr_block = "10.1.0.0/16"