Skip to content

Commit

Permalink
Merge pull request #11104 from terraform-providers/rfd-instance-role-…
Browse files Browse the repository at this point in the history
…change

Change instance profile if the instance is stopped
  • Loading branch information
ryndaniels authored Jan 8, 2020
2 parents 212df20 + d6326ce commit 7a6a14e
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 54 deletions.
146 changes: 92 additions & 54 deletions aws/resource_aws_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -953,29 +953,9 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
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 {
input := &ec2.AssociateIamInstanceProfileInput{
InstanceId: aws.String(d.Id()),
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{
Name: aws.String(d.Get("iam_instance_profile").(string)),
},
}
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
_, err := conn.AssociateIamInstanceProfile(input)
if err != nil {
if isAWSErr(err, "InvalidParameterValue", "Invalid IAM Instance Profile") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
})
if isResourceTimeoutError(err) {
_, err = conn.AssociateIamInstanceProfile(input)
}
if err != nil {
return fmt.Errorf("Error associating instance with instance profile: %s", err)
if err := associateInstanceProfile(d, conn); err != nil {
return err
}

} else {
// Has an Iam Instance Profile associated with it, need to replace the association
associationId := resp.IamInstanceProfileAssociations[0].AssociationId
Expand All @@ -985,33 +965,45 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
Name: aws.String(d.Get("iam_instance_profile").(string)),
},
}
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
_, err := conn.ReplaceIamInstanceProfileAssociation(input)
if err != nil {
if isAWSErr(err, "InvalidParameterValue", "Invalid IAM Instance Profile") {
return resource.RetryableError(err)

// If the instance is running, we can replace the instance profile association.
// If it is stopped, the association must be removed and the new one attached separately. (GH-8262)
instanceState := d.Get("instance_state").(string)

if instanceState != "" {
if instanceState == ec2.InstanceStateNameStopped || instanceState == ec2.InstanceStateNameStopping || instanceState == ec2.InstanceStateNameShuttingDown {
if err := disassociateInstanceProfile(associationId, conn); err != nil {
return err
}
if err := associateInstanceProfile(d, conn); err != nil {
return err
}
} else {
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
_, err := conn.ReplaceIamInstanceProfileAssociation(input)
if err != nil {
if isAWSErr(err, "InvalidParameterValue", "Invalid IAM Instance Profile") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
})
if isResourceTimeoutError(err) {
_, err = conn.ReplaceIamInstanceProfileAssociation(input)
}
if err != nil {
return fmt.Errorf("Error replacing instance profile association: %s", err)
}
return resource.NonRetryableError(err)
}
return nil
})
if isResourceTimeoutError(err) {
_, err = conn.ReplaceIamInstanceProfileAssociation(input)
}
if err != nil {
return fmt.Errorf("Error replacing instance profile association: %s", 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 {
if err := disassociateInstanceProfile(associationId, conn); err != nil {
return err
}
}
Expand Down Expand Up @@ -1107,19 +1099,8 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("error stopping instance (%s): %s", d.Id(), err)
}

stateConf := &resource.StateChangeConf{
Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
Target: []string{"stopped"},
Refresh: InstanceStateRefreshFunc(conn, d.Id(), []string{}),
Timeout: d.Timeout(schema.TimeoutUpdate),
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}

_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf(
"Error waiting for instance (%s) to stop: %s", d.Id(), err)
if err := waitForInstanceStopping(conn, d.Id(), 10*time.Minute); err != nil {
return err
}

log.Printf("[INFO] Modifying instance type %s", d.Id())
Expand All @@ -1141,7 +1122,7 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("error starting instance (%s): %s", d.Id(), err)
}

stateConf = &resource.StateChangeConf{
stateConf := &resource.StateChangeConf{
Pending: []string{"pending", "stopped"},
Target: []string{"running"},
Refresh: InstanceStateRefreshFunc(conn, d.Id(), []string{"terminated"}),
Expand Down Expand Up @@ -1314,6 +1295,42 @@ func readBlockDevices(d *schema.ResourceData, instance *ec2.Instance, conn *ec2.
return nil
}

func associateInstanceProfile(d *schema.ResourceData, conn *ec2.EC2) error {
input := &ec2.AssociateIamInstanceProfileInput{
InstanceId: aws.String(d.Id()),
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{
Name: aws.String(d.Get("iam_instance_profile").(string)),
},
}
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
_, err := conn.AssociateIamInstanceProfile(input)
if err != nil {
if isAWSErr(err, "InvalidParameterValue", "Invalid IAM Instance Profile") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
})
if isResourceTimeoutError(err) {
_, err = conn.AssociateIamInstanceProfile(input)
}
if err != nil {
return fmt.Errorf("error associating instance with instance profile: %s", err)
}
return nil
}

func disassociateInstanceProfile(associationId *string, conn *ec2.EC2) error {
_, err := conn.DisassociateIamInstanceProfile(&ec2.DisassociateIamInstanceProfileInput{
AssociationId: associationId,
})
if err != nil {
return fmt.Errorf("error disassociating instance with instance profile: %w", err)
}
return nil
}

func readBlockDevicesFromInstance(instance *ec2.Instance, conn *ec2.EC2) (map[string]interface{}, error) {
blockDevices := make(map[string]interface{})
blockDevices["ebs"] = make([]map[string]interface{}, 0)
Expand Down Expand Up @@ -1962,6 +1979,27 @@ func awsTerminateInstance(conn *ec2.EC2, id string, timeout time.Duration) error
return waitForInstanceDeletion(conn, id, timeout)
}

func waitForInstanceStopping(conn *ec2.EC2, id string, timeout time.Duration) error {
log.Printf("[DEBUG] Waiting for instance (%s) to become stopped", id)

stateConf := &resource.StateChangeConf{
Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
Target: []string{"stopped"},
Refresh: InstanceStateRefreshFunc(conn, id, []string{}),
Timeout: timeout,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}

_, err := stateConf.WaitForState()
if err != nil {
return fmt.Errorf(
"error waiting for instance (%s) to stop: %s", id, err)
}

return nil
}

func waitForInstanceDeletion(conn *ec2.EC2, id string, timeout time.Duration) error {
log.Printf("[DEBUG] Waiting for instance (%s) to become terminated", id)

Expand Down
29 changes: 29 additions & 0 deletions aws/resource_aws_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,7 @@ func TestAccAWSInstance_instanceProfileChange(t *testing.T) {
var v ec2.Instance
resourceName := "aws_instance.test"
rName := fmt.Sprintf("tf-testacc-instance-%s", acctest.RandStringFromCharSet(12, acctest.CharSetAlphaNum))
rName2 := fmt.Sprintf("tf-testacc-instance-%s", acctest.RandStringFromCharSet(12, acctest.CharSetAlphaNum))

testCheckInstanceProfile := func() resource.TestCheckFunc {
return func(*terraform.State) error {
Expand Down Expand Up @@ -1273,6 +1274,19 @@ func TestAccAWSInstance_instanceProfileChange(t *testing.T) {
testCheckInstanceProfile(),
),
},
{
Config: testAccInstanceConfigWithInstanceProfile(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckStopInstance(&v), // GH-8262
),
},
{
Config: testAccInstanceConfigWithInstanceProfile(rName2),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceExists(resourceName, &v),
testCheckInstanceProfile(),
),
},
},
})
}
Expand Down Expand Up @@ -2580,6 +2594,21 @@ func testAccCheckInstanceDisappears(conf *ec2.Instance) resource.TestCheckFunc {
}
}

func testAccCheckStopInstance(conf *ec2.Instance) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn

params := &ec2.StopInstancesInput{
InstanceIds: []*string{conf.InstanceId},
}
if _, err := conn.StopInstances(params); err != nil {
return err
}

return waitForInstanceStopping(conn, *conf.InstanceId, 10*time.Minute)
}
}

func TestInstanceTenancySchema(t *testing.T) {
actualSchema := resourceAwsInstance().Schema["tenancy"]
expectedSchema := &schema.Schema{
Expand Down

0 comments on commit 7a6a14e

Please sign in to comment.