Skip to content

Commit

Permalink
resource/aws_instance: Apply attribute waiter logic to iam_instance_p…
Browse files Browse the repository at this point in the history
…rofile attribute

Reference: #16769

This resource attribute has long been the cause of the following flakey acceptance test:

```
=== CONT  TestAccAWSInstance_instanceProfileChange
TestAccAWSInstance_instanceProfileChange: resource_aws_instance_test.go:1134: Step 3/5 error: After applying this test step, the plan was not empty.
stdout:
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
~ resource "aws_instance" "test" {
+ iam_instance_profile         = "tf-testacc-instance-1obw9bpsudzr"
id                           = "i-0748e5ca403753317"
tags                         = {
"Name" = "tf-testacc-instance-1obw9bpsudzr"
}
}
```

Adding logic, similar to `SubnetMapCustomerOwnedIpOnLaunchUpdated` in #16676 can be used to ensure the attribute value has flipped correctly after calling the `AssociateIamInstanceProfile`, `DisassociateIamInstanceProfile` or `ReplaceIamInstanceProfile` APIs.

This attribute waiter setup will be added to a forthcoming Retries and Waiters section in the Contribution Guide.

Output from acceptance testing in AWS Commercial:

```
--- PASS: TestAccAWSInstance_addSecondaryInterface (242.73s)
--- PASS: TestAccAWSInstance_addSecurityGroupNetworkInterface (230.14s)
--- PASS: TestAccAWSInstance_associatePublic_defaultPrivate (110.25s)
--- PASS: TestAccAWSInstance_associatePublic_defaultPublic (110.99s)
--- PASS: TestAccAWSInstance_associatePublic_explicitPrivate (100.21s)
--- PASS: TestAccAWSInstance_associatePublic_explicitPublic (110.40s)
--- PASS: TestAccAWSInstance_associatePublic_overridePrivate (109.74s)
--- PASS: TestAccAWSInstance_associatePublic_overridePublic (131.67s)
--- PASS: TestAccAWSInstance_associatePublicIPAndPrivateIP (146.51s)
--- PASS: TestAccAWSInstance_atLeastOneOtherEbsVolume (193.28s)
--- PASS: TestAccAWSInstance_basic (88.80s)
--- PASS: TestAccAWSInstance_blockDevices (121.48s)
--- PASS: TestAccAWSInstance_blockDeviceTags_ebsAndRoot (210.18s)
--- PASS: TestAccAWSInstance_blockDeviceTags_volumeTags (218.14s)
--- PASS: TestAccAWSInstance_blockDeviceTags_withAttachedVolume (274.15s)
--- PASS: TestAccAWSInstance_changeInstanceType (279.18s)
--- PASS: TestAccAWSInstance_CreditSpecification_Empty_NonBurstable (146.18s)
--- PASS: TestAccAWSInstance_creditSpecification_isNotAppliedToNonBurstable (121.58s)
--- PASS: TestAccAWSInstance_creditSpecification_standardCpuCredits (151.11s)
--- PASS: TestAccAWSInstance_creditSpecification_standardCpuCredits_t2Tot3Taint (417.38s)
--- PASS: TestAccAWSInstance_creditSpecification_unknownCpuCredits_t2 (108.53s)
--- PASS: TestAccAWSInstance_creditSpecification_unknownCpuCredits_t3 (139.87s)
--- PASS: TestAccAWSInstance_creditSpecification_unlimitedCpuCredits (142.36s)
--- PASS: TestAccAWSInstance_creditSpecification_unlimitedCpuCredits_t2Tot3Taint (209.11s)
--- PASS: TestAccAWSInstance_creditSpecification_unspecifiedDefaultsToStandard (111.75s)
--- PASS: TestAccAWSInstance_CreditSpecification_UnspecifiedToEmpty_NonBurstable (114.39s)
--- PASS: TestAccAWSInstance_creditSpecification_updateCpuCredits (180.11s)
--- PASS: TestAccAWSInstance_creditSpecificationT3_standardCpuCredits (136.87s)
--- PASS: TestAccAWSInstance_creditSpecificationT3_unlimitedCpuCredits (125.41s)
--- PASS: TestAccAWSInstance_creditSpecificationT3_unspecifiedDefaultsToUnlimited (127.50s)
--- PASS: TestAccAWSInstance_creditSpecificationT3_updateCpuCredits (180.12s)
--- PASS: TestAccAWSInstance_dedicatedInstance (133.35s)
--- PASS: TestAccAWSInstance_disableApiTermination (184.77s)
--- PASS: TestAccAWSInstance_disappears (78.61s)
--- PASS: TestAccAWSInstance_EbsBlockDevice_InvalidIopsForVolumeType (12.67s)
--- PASS: TestAccAWSInstance_EbsBlockDevice_InvalidThroughputForVolumeType (10.14s)
--- PASS: TestAccAWSInstance_EbsBlockDevice_KmsKeyArn (117.10s)
--- PASS: TestAccAWSInstance_EbsRootDevice_basic (193.58s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifyAll (152.37s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifyDeleteOnTermination (153.09s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifyIOPS_Io1 (169.55s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifyIOPS_Io2 (194.13s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifySize (165.98s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifyThroughput_Gp3 (163.26s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifyType (183.84s)
--- PASS: TestAccAWSInstance_EbsRootDevice_MultipleBlockDevices_ModifyDeleteOnTermination (147.87s)
--- PASS: TestAccAWSInstance_EbsRootDevice_MultipleBlockDevices_ModifySize (192.81s)
--- PASS: TestAccAWSInstance_EbsRootDevice_MultipleDynamicEBSBlockDevices (232.62s)
--- PASS: TestAccAWSInstance_Empty_PrivateIP (145.38s)
--- PASS: TestAccAWSInstance_enclaveOptions (219.52s)
--- PASS: TestAccAWSInstance_forceNewAndTagsDrift (283.16s)
--- PASS: TestAccAWSInstance_getPasswordData_falseToTrue (157.98s)
--- PASS: TestAccAWSInstance_getPasswordData_trueToFalse (198.56s)
--- PASS: TestAccAWSInstance_GP2IopsDevice (99.64s)
--- PASS: TestAccAWSInstance_GP2WithIopsValue (43.96s)
--- PASS: TestAccAWSInstance_hibernation (236.92s)
--- PASS: TestAccAWSInstance_inDefaultVpcBySgId (103.41s)
--- PASS: TestAccAWSInstance_inDefaultVpcBySgName (131.23s)
--- PASS: TestAccAWSInstance_inEc2Classic (107.37s)
--- PASS: TestAccAWSInstance_instanceProfileChange (328.37s)
--- PASS: TestAccAWSInstance_ipv6_supportAddressCount (145.65s)
--- PASS: TestAccAWSInstance_ipv6_supportAddressCountWithIpv4 (101.01s)
--- PASS: TestAccAWSInstance_ipv6AddressCountAndSingleAddressCausesError (19.16s)
--- PASS: TestAccAWSInstance_keyPairCheck (131.83s)
--- PASS: TestAccAWSInstance_metadataOptions (157.61s)
--- PASS: TestAccAWSInstance_NetworkInstanceRemovingAllSecurityGroups (189.12s)
--- PASS: TestAccAWSInstance_NetworkInstanceSecurityGroups (162.78s)
--- PASS: TestAccAWSInstance_NetworkInstanceVPCSecurityGroupIDs (157.66s)
--- PASS: TestAccAWSInstance_NewNetworkInterface_EmptyPrivateIPAndSecondaryPrivateIPs (149.96s)
--- PASS: TestAccAWSInstance_NewNetworkInterface_EmptyPrivateIPAndSecondaryPrivateIPsUpdate (220.50s)
--- PASS: TestAccAWSInstance_NewNetworkInterface_PrivateIPAndSecondaryPrivateIPs (121.07s)
--- PASS: TestAccAWSInstance_NewNetworkInterface_PrivateIPAndSecondaryPrivateIPsUpdate (180.92s)
--- PASS: TestAccAWSInstance_NewNetworkInterface_PublicIPAndSecondaryPrivateIPs (297.98s)
--- PASS: TestAccAWSInstance_noAMIEphemeralDevices (121.40s)
--- PASS: TestAccAWSInstance_placementGroup (121.32s)
--- PASS: TestAccAWSInstance_primaryNetworkInterface (157.80s)
--- PASS: TestAccAWSInstance_primaryNetworkInterfaceSourceDestCheck (189.25s)
--- PASS: TestAccAWSInstance_privateIP (147.33s)
--- PASS: TestAccAWSInstance_RootBlockDevice_KmsKeyArn (120.64s)
--- PASS: TestAccAWSInstance_rootBlockDeviceMismatch (162.61s)
--- PASS: TestAccAWSInstance_rootInstanceStore (101.64s)
--- PASS: TestAccAWSInstance_sourceDestCheck (238.40s)
--- PASS: TestAccAWSInstance_tags (143.95s)
--- PASS: TestAccAWSInstance_UserData_EmptyStringToUnspecified (157.16s)
--- PASS: TestAccAWSInstance_UserData_UnspecifiedToEmptyString (136.23s)
--- PASS: TestAccAWSInstance_userDataBase64 (156.05s)
--- PASS: TestAccAWSInstance_withIamInstanceProfile (127.90s)
--- SKIP: TestAccAWSInstance_outpost (1.88s)

--- PASS: TestAccAWSInstanceDataSource_AzUserData (106.26s)
--- PASS: TestAccAWSInstanceDataSource_basic (110.08s)
--- PASS: TestAccAWSInstanceDataSource_blockDevices (117.74s)
--- PASS: TestAccAWSInstanceDataSource_blockDeviceTags (121.08s)
--- PASS: TestAccAWSInstanceDataSource_creditSpecification (116.29s)
--- PASS: TestAccAWSInstanceDataSource_EbsBlockDevice_KmsKeyId (136.51s)
--- PASS: TestAccAWSInstanceDataSource_enclaveOptions (87.52s)
--- PASS: TestAccAWSInstanceDataSource_getPasswordData_falseToTrue (188.34s)
--- PASS: TestAccAWSInstanceDataSource_getPasswordData_trueToFalse (275.27s)
--- PASS: TestAccAWSInstanceDataSource_GetUserData (208.51s)
--- PASS: TestAccAWSInstanceDataSource_GetUserData_NoUserData (234.58s)
--- PASS: TestAccAWSInstanceDataSource_gp2IopsDevice (98.45s)
--- PASS: TestAccAWSInstanceDataSource_gp3ThroughputDevice (100.55s)
--- PASS: TestAccAWSInstanceDataSource_keyPair (111.47s)
--- PASS: TestAccAWSInstanceDataSource_metadataOptions (122.07s)
--- PASS: TestAccAWSInstanceDataSource_PlacementGroup (129.96s)
--- PASS: TestAccAWSInstanceDataSource_privateIP (113.30s)
--- PASS: TestAccAWSInstanceDataSource_RootBlockDevice_KmsKeyId (110.23s)
--- PASS: TestAccAWSInstanceDataSource_rootInstanceStore (120.74s)
--- PASS: TestAccAWSInstanceDataSource_secondaryPrivateIPs (125.40s)
--- PASS: TestAccAWSInstanceDataSource_SecurityGroups (126.16s)
--- PASS: TestAccAWSInstanceDataSource_tags (96.07s)
--- PASS: TestAccAWSInstanceDataSource_VPC (126.74s)
--- PASS: TestAccAWSInstanceDataSource_VPCSecurityGroups (135.88s)
```

Output from acceptance testing in AWS GovCloud (US):

```
--- FAIL: TestAccAWSInstance_dedicatedInstance (31.08s) # #17412
--- FAIL: TestAccAWSInstance_EbsRootDevice_ModifyIOPS_Io2 (12.77s) # #17412
--- FAIL: TestAccAWSInstance_enclaveOptions (31.10s) # #17412
--- PASS: TestAccAWSInstance_addSecondaryInterface (201.36s)
--- PASS: TestAccAWSInstance_addSecurityGroupNetworkInterface (169.84s)
--- PASS: TestAccAWSInstance_associatePublic_defaultPrivate (100.80s)
--- PASS: TestAccAWSInstance_associatePublic_defaultPublic (112.34s)
--- PASS: TestAccAWSInstance_associatePublic_explicitPrivate (94.40s)
--- PASS: TestAccAWSInstance_associatePublic_explicitPublic (112.61s)
--- PASS: TestAccAWSInstance_associatePublic_overridePrivate (93.90s)
--- PASS: TestAccAWSInstance_associatePublic_overridePublic (83.17s)
--- PASS: TestAccAWSInstance_associatePublicIPAndPrivateIP (98.45s)
--- PASS: TestAccAWSInstance_atLeastOneOtherEbsVolume (174.93s)
--- PASS: TestAccAWSInstance_basic (90.77s)
--- PASS: TestAccAWSInstance_blockDevices (124.40s)
--- PASS: TestAccAWSInstance_blockDeviceTags_ebsAndRoot (144.71s)
--- PASS: TestAccAWSInstance_blockDeviceTags_volumeTags (189.71s)
--- PASS: TestAccAWSInstance_blockDeviceTags_withAttachedVolume (184.30s)
--- PASS: TestAccAWSInstance_changeInstanceType (178.33s)
--- PASS: TestAccAWSInstance_CreditSpecification_Empty_NonBurstable (140.47s)
--- PASS: TestAccAWSInstance_creditSpecification_isNotAppliedToNonBurstable (111.53s)
--- PASS: TestAccAWSInstance_creditSpecification_standardCpuCredits (120.43s)
--- PASS: TestAccAWSInstance_creditSpecification_standardCpuCredits_t2Tot3Taint (223.58s)
--- PASS: TestAccAWSInstance_creditSpecification_unknownCpuCredits_t2 (98.06s)
--- PASS: TestAccAWSInstance_creditSpecification_unknownCpuCredits_t3 (90.11s)
--- PASS: TestAccAWSInstance_creditSpecification_unlimitedCpuCredits (131.18s)
--- PASS: TestAccAWSInstance_creditSpecification_unlimitedCpuCredits_t2Tot3Taint (196.64s)
--- PASS: TestAccAWSInstance_creditSpecification_unspecifiedDefaultsToStandard (101.42s)
--- PASS: TestAccAWSInstance_CreditSpecification_UnspecifiedToEmpty_NonBurstable (119.74s)
--- PASS: TestAccAWSInstance_creditSpecification_updateCpuCredits (184.44s)
--- PASS: TestAccAWSInstance_creditSpecificationT3_standardCpuCredits (135.21s)
--- PASS: TestAccAWSInstance_creditSpecificationT3_unlimitedCpuCredits (167.30s)
--- PASS: TestAccAWSInstance_creditSpecificationT3_unspecifiedDefaultsToUnlimited (100.56s)
--- PASS: TestAccAWSInstance_creditSpecificationT3_updateCpuCredits (183.55s)
--- PASS: TestAccAWSInstance_disableApiTermination (181.92s)
--- PASS: TestAccAWSInstance_disappears (194.95s)
--- PASS: TestAccAWSInstance_EbsBlockDevice_InvalidIopsForVolumeType (11.73s)
--- PASS: TestAccAWSInstance_EbsBlockDevice_InvalidThroughputForVolumeType (10.91s)
--- PASS: TestAccAWSInstance_EbsBlockDevice_KmsKeyArn (109.41s)
--- PASS: TestAccAWSInstance_EbsRootDevice_basic (109.65s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifyAll (133.86s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifyDeleteOnTermination (147.04s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifyIOPS_Io1 (166.57s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifySize (156.04s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifyThroughput_Gp3 (134.16s)
--- PASS: TestAccAWSInstance_EbsRootDevice_ModifyType (123.90s)
--- PASS: TestAccAWSInstance_EbsRootDevice_MultipleBlockDevices_ModifyDeleteOnTermination (134.58s)
--- PASS: TestAccAWSInstance_EbsRootDevice_MultipleBlockDevices_ModifySize (141.10s)
--- PASS: TestAccAWSInstance_EbsRootDevice_MultipleDynamicEBSBlockDevices (202.53s)
--- PASS: TestAccAWSInstance_Empty_PrivateIP (116.78s)
--- PASS: TestAccAWSInstance_forceNewAndTagsDrift (173.61s)
--- PASS: TestAccAWSInstance_getPasswordData_falseToTrue (146.02s)
--- PASS: TestAccAWSInstance_getPasswordData_trueToFalse (191.54s)
--- PASS: TestAccAWSInstance_GP2IopsDevice (86.02s)
--- PASS: TestAccAWSInstance_GP2WithIopsValue (11.25s)
--- PASS: TestAccAWSInstance_hibernation (256.05s)
--- PASS: TestAccAWSInstance_inDefaultVpcBySgId (93.32s)
--- PASS: TestAccAWSInstance_inDefaultVpcBySgName (106.90s)
--- PASS: TestAccAWSInstance_instanceProfileChange (248.34s)
--- PASS: TestAccAWSInstance_ipv6_supportAddressCount (112.67s)
--- PASS: TestAccAWSInstance_ipv6_supportAddressCountWithIpv4 (114.70s)
--- PASS: TestAccAWSInstance_ipv6AddressCountAndSingleAddressCausesError (29.25s)
--- PASS: TestAccAWSInstance_keyPairCheck (98.40s)
--- PASS: TestAccAWSInstance_metadataOptions (185.29s)
--- PASS: TestAccAWSInstance_NetworkInstanceRemovingAllSecurityGroups (148.16s)
--- PASS: TestAccAWSInstance_NetworkInstanceSecurityGroups (137.85s)
--- PASS: TestAccAWSInstance_NetworkInstanceVPCSecurityGroupIDs (123.37s)
--- PASS: TestAccAWSInstance_NewNetworkInterface_EmptyPrivateIPAndSecondaryPrivateIPs (135.65s)
--- PASS: TestAccAWSInstance_NewNetworkInterface_EmptyPrivateIPAndSecondaryPrivateIPsUpdate (182.43s)
--- PASS: TestAccAWSInstance_NewNetworkInterface_PrivateIPAndSecondaryPrivateIPs (101.76s)
--- PASS: TestAccAWSInstance_NewNetworkInterface_PrivateIPAndSecondaryPrivateIPsUpdate (187.35s)
--- PASS: TestAccAWSInstance_NewNetworkInterface_PublicIPAndSecondaryPrivateIPs (197.08s)
--- PASS: TestAccAWSInstance_noAMIEphemeralDevices (92.96s)
--- PASS: TestAccAWSInstance_placementGroup (126.79s)
--- PASS: TestAccAWSInstance_primaryNetworkInterface (156.42s)
--- PASS: TestAccAWSInstance_primaryNetworkInterfaceSourceDestCheck (125.43s)
--- PASS: TestAccAWSInstance_privateIP (106.48s)
--- PASS: TestAccAWSInstance_RootBlockDevice_KmsKeyArn (80.05s)
--- PASS: TestAccAWSInstance_rootInstanceStore (111.78s)
--- PASS: TestAccAWSInstance_sourceDestCheck (213.01s)
--- PASS: TestAccAWSInstance_tags (128.83s)
--- PASS: TestAccAWSInstance_UserData_EmptyStringToUnspecified (148.75s)
--- PASS: TestAccAWSInstance_UserData_UnspecifiedToEmptyString (158.81s)
--- PASS: TestAccAWSInstance_userDataBase64 (117.21s)
--- PASS: TestAccAWSInstance_withIamInstanceProfile (123.11s)
--- SKIP: TestAccAWSInstance_inEc2Classic (1.40s)
--- SKIP: TestAccAWSInstance_outpost (0.70s)
--- SKIP: TestAccAWSInstance_rootBlockDeviceMismatch (0.00s)

--- FAIL: TestAccAWSInstanceDataSource_enclaveOptions (33.30s) # #17412
--- PASS: TestAccAWSInstanceDataSource_AzUserData (86.49s)
--- PASS: TestAccAWSInstanceDataSource_basic (119.95s)
--- PASS: TestAccAWSInstanceDataSource_blockDevices (128.42s)
--- PASS: TestAccAWSInstanceDataSource_blockDeviceTags (130.34s)
--- PASS: TestAccAWSInstanceDataSource_creditSpecification (119.83s)
--- PASS: TestAccAWSInstanceDataSource_EbsBlockDevice_KmsKeyId (101.52s)
--- PASS: TestAccAWSInstanceDataSource_getPasswordData_falseToTrue (175.43s)
--- PASS: TestAccAWSInstanceDataSource_getPasswordData_trueToFalse (225.22s)
--- PASS: TestAccAWSInstanceDataSource_GetUserData (240.00s)
--- PASS: TestAccAWSInstanceDataSource_GetUserData_NoUserData (226.72s)
--- PASS: TestAccAWSInstanceDataSource_gp2IopsDevice (99.37s)
--- PASS: TestAccAWSInstanceDataSource_gp3ThroughputDevice (98.23s)
--- PASS: TestAccAWSInstanceDataSource_keyPair (120.55s)
--- PASS: TestAccAWSInstanceDataSource_metadataOptions (131.12s)
--- PASS: TestAccAWSInstanceDataSource_PlacementGroup (150.17s)
--- PASS: TestAccAWSInstanceDataSource_privateIP (112.23s)
--- PASS: TestAccAWSInstanceDataSource_RootBlockDevice_KmsKeyId (159.42s)
--- PASS: TestAccAWSInstanceDataSource_rootInstanceStore (128.18s)
--- PASS: TestAccAWSInstanceDataSource_secondaryPrivateIPs (117.31s)
--- PASS: TestAccAWSInstanceDataSource_SecurityGroups (114.26s)
--- PASS: TestAccAWSInstanceDataSource_tags (117.87s)
--- PASS: TestAccAWSInstanceDataSource_VPC (121.68s)
--- PASS: TestAccAWSInstanceDataSource_VPCSecurityGroups (144.39s)
```
  • Loading branch information
bflad committed Feb 2, 2021
1 parent 075f130 commit 75ac23b
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 10 deletions.
14 changes: 13 additions & 1 deletion aws/data_source_aws_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/hashcode"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
tfiam "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam"
)

func dataSourceAwsInstance() *schema.Resource {
Expand Down Expand Up @@ -458,7 +459,18 @@ func instanceDescriptionAttributes(d *schema.ResourceData, instance *ec2.Instanc
d.Set("private_dns", instance.PrivateDnsName)
d.Set("private_ip", instance.PrivateIpAddress)
d.Set("outpost_arn", instance.OutpostArn)
d.Set("iam_instance_profile", iamInstanceProfileArnToName(instance.IamInstanceProfile))

if instance.IamInstanceProfile != nil && instance.IamInstanceProfile.Arn != nil {
name, err := tfiam.InstanceProfileARNToName(aws.StringValue(instance.IamInstanceProfile.Arn))

if err != nil {
return fmt.Errorf("error setting iam_instance_profile: %w", err)
}

d.Set("iam_instance_profile", name)
} else {
d.Set("iam_instance_profile", nil)
}

// iterate through network interfaces, and set subnet, network_interface, public_addr
if len(instance.NetworkInterfaces) > 0 {
Expand Down
4 changes: 4 additions & 0 deletions aws/internal/service/ec2/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const (
ErrCodeClientVpnRouteNotFound = "InvalidClientVpnRouteNotFound"
)

const (
ErrCodeInvalidInstanceIDNotFound = "InvalidInstanceID.NotFound"
)

const (
InvalidSecurityGroupIDNotFound = "InvalidSecurityGroupID.NotFound"
InvalidGroupNotFound = "InvalidGroup.NotFound"
Expand Down
19 changes: 19 additions & 0 deletions aws/internal/service/ec2/finder/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,25 @@ func ClientVpnRouteByID(conn *ec2.EC2, routeID string) (*ec2.DescribeClientVpnRo
return ClientVpnRoute(conn, endpointID, targetSubnetID, destinationCidr)
}

// InstanceByID looks up a Instance by ID. When not found, returns nil and potentially an API error.
func InstanceByID(conn *ec2.EC2, id string) (*ec2.Instance, error) {
input := &ec2.DescribeInstancesInput{
InstanceIds: aws.StringSlice([]string{id}),
}

output, err := conn.DescribeInstances(input)

if err != nil {
return nil, err
}

if output == nil || len(output.Reservations) == 0 || output.Reservations[0] == nil || len(output.Reservations[0].Instances) == 0 || output.Reservations[0].Instances[0] == nil {
return nil, nil
}

return output.Reservations[0].Instances[0], nil
}

// SecurityGroupByID looks up a security group by ID. When not found, returns nil and potentially an API error.
func SecurityGroupByID(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) {
req := &ec2.DescribeSecurityGroupsInput{
Expand Down
35 changes: 35 additions & 0 deletions aws/internal/service/ec2/waiter/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder"
tfiam "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam"
)

const (
Expand Down Expand Up @@ -210,6 +211,40 @@ func ClientVpnRouteStatus(conn *ec2.EC2, routeID string) resource.StateRefreshFu
}
}

// InstanceIamInstanceProfile fetches the Instance and its IamInstanceProfile
//
// The EC2 API accepts a name and always returns an ARN, so it is converted
// back to the name to prevent unexpected differences.
func InstanceIamInstanceProfile(conn *ec2.EC2, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
instance, err := finder.InstanceByID(conn, id)

if tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidInstanceIDNotFound) {
return nil, "", nil
}

if err != nil {
return nil, "", err
}

if instance == nil {
return nil, "", nil
}

if instance.IamInstanceProfile == nil || instance.IamInstanceProfile.Arn == nil {
return instance, "", nil
}

name, err := tfiam.InstanceProfileARNToName(aws.StringValue(instance.IamInstanceProfile.Arn))

if err != nil {
return instance, "", err
}

return instance, name, nil
}
}

const (
SecurityGroupStatusCreated = "Created"

Expand Down
18 changes: 18 additions & 0 deletions aws/internal/service/ec2/waiter/waiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,24 @@ func ClientVpnRouteDeleted(conn *ec2.EC2, routeID string) (*ec2.ClientVpnRoute,
return nil, err
}

func InstanceIamInstanceProfileUpdated(conn *ec2.EC2, instanceID string, expectedValue string) (*ec2.Instance, error) {
stateConf := &resource.StateChangeConf{
Target: []string{expectedValue},
Refresh: InstanceIamInstanceProfile(conn, instanceID),
Timeout: InstanceAttributePropagationTimeout,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*ec2.Instance); ok {
return output, err
}

return nil, err
}

func SecurityGroupCreated(conn *ec2.EC2, id string, timeout time.Duration) (*ec2.SecurityGroup, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{SecurityGroupStatusNotFound},
Expand Down
40 changes: 40 additions & 0 deletions aws/internal/service/iam/arn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package iam

import (
"fmt"
"strings"

"github.com/aws/aws-sdk-go/aws/arn"
)

const (
ARNSeparator = "/"
ARNService = "iam"

InstanceProfileResourcePrefix = "instance-profile"
)

// InstanceProfileARNToName converts Amazon Resource Name (ARN) to Name.
func InstanceProfileARNToName(inputARN string) (string, error) {
parsedARN, err := arn.Parse(inputARN)

if err != nil {
return "", fmt.Errorf("error parsing ARN (%s): %w", inputARN, err)
}

if actual, expected := parsedARN.Service, ARNService; actual != expected {
return "", fmt.Errorf("expected service %s in ARN (%s), got: %s", expected, inputARN, actual)
}

resourceParts := strings.Split(parsedARN.Resource, ARNSeparator)

if actual, expected := len(resourceParts), 2; actual != expected {
return "", fmt.Errorf("expected %d resource parts in ARN (%s), got: %d", expected, inputARN, actual)
}

if actual, expected := resourceParts[0], InstanceProfileResourcePrefix; actual != expected {
return "", fmt.Errorf("expected resource prefix %s in ARN (%s), got: %s", expected, inputARN, actual)
}

return resourceParts[1], nil
}
70 changes: 70 additions & 0 deletions aws/internal/service/iam/arn_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package iam_test

import (
"regexp"
"testing"

"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam"
)

func TestInstanceProfileARNToName(t *testing.T) {
testCases := []struct {
TestName string
InputARN string
ExpectedError *regexp.Regexp
ExpectedName string
}{
{
TestName: "empty ARN",
InputARN: "",
ExpectedError: regexp.MustCompile(`error parsing ARN`),
},
{
TestName: "unparsable ARN",
InputARN: "test",
ExpectedError: regexp.MustCompile(`error parsing ARN`),
},
{
TestName: "invalid ARN service",
InputARN: "arn:aws:ec2:us-east-1:123456789012:instance/i-12345678",
ExpectedError: regexp.MustCompile(`expected service iam`),
},
{
TestName: "invalid ARN resource parts",
InputARN: "arn:aws:iam:us-east-1:123456789012:instance-profile/test/name",
ExpectedError: regexp.MustCompile(`expected 2 resource parts`),
},
{
TestName: "invalid ARN resource prefix",
InputARN: "arn:aws:iam:us-east-1:123456789012:role/name",
ExpectedError: regexp.MustCompile(`expected resource prefix instance-profile`),
},
{
TestName: "valid ARN",
InputARN: "arn:aws:iam:us-east-1:123456789012:instance-profile/name",
ExpectedName: "name",
},
}

for _, testCase := range testCases {
t.Run(testCase.TestName, func(t *testing.T) {
got, err := iam.InstanceProfileARNToName(testCase.InputARN)

if err == nil && testCase.ExpectedError != nil {
t.Fatalf("expected error %s, got no error", testCase.ExpectedError.String())
}

if err != nil && testCase.ExpectedError == nil {
t.Fatalf("got unexpected error: %s", err)
}

if err != nil && !testCase.ExpectedError.MatchString(err.Error()) {
t.Fatalf("expected error %s, got: %s", testCase.ExpectedError.String(), err)
}

if got != testCase.ExpectedName {
t.Errorf("got %s, expected %s", got, testCase.ExpectedName)
}
})
}
}
26 changes: 17 additions & 9 deletions aws/resource_aws_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
tfec2 "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter"
tfiam "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

Expand Down Expand Up @@ -813,7 +814,18 @@ func resourceAwsInstanceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("private_dns", instance.PrivateDnsName)
d.Set("private_ip", instance.PrivateIpAddress)
d.Set("outpost_arn", instance.OutpostArn)
d.Set("iam_instance_profile", iamInstanceProfileArnToName(instance.IamInstanceProfile))

if instance.IamInstanceProfile != nil && instance.IamInstanceProfile.Arn != nil {
name, err := tfiam.InstanceProfileARNToName(aws.StringValue(instance.IamInstanceProfile.Arn))

if err != nil {
return fmt.Errorf("error setting iam_instance_profile: %w", err)
}

d.Set("iam_instance_profile", name)
} else {
d.Set("iam_instance_profile", nil)
}

// Set configured Network Interface Device Index Slice
// We only want to read, and populate state for the configured network_interface attachments. Otherwise, other
Expand Down Expand Up @@ -1109,6 +1121,10 @@ func resourceAwsInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
}
}
}

if _, err := waiter.InstanceIamInstanceProfileUpdated(conn, d.Id(), d.Get("iam_instance_profile").(string)); err != nil {
return fmt.Errorf("error waiting for EC2 Instance (%s) IAM Instance Profile update: %w", d.Id(), err)
}
}

// SourceDestCheck can only be modified on an instance without manually specified network interfaces.
Expand Down Expand Up @@ -2466,14 +2482,6 @@ func waitForInstanceDeletion(conn *ec2.EC2, id string, timeout time.Duration) er
return nil
}

func iamInstanceProfileArnToName(ip *ec2.IamInstanceProfile) string {
if ip == nil || ip.Arn == nil {
return ""
}
parts := strings.Split(aws.StringValue(ip.Arn), "/")
return parts[len(parts)-1]
}

func userDataHashSum(user_data string) string {
// Check whether the user_data is not Base64 encoded.
// Always calculate hash of base64 decoded value since we
Expand Down

0 comments on commit 75ac23b

Please sign in to comment.