Skip to content

Commit

Permalink
resource/aws_iam_instance_profile: Add tagging support + sweeper (#17962
Browse files Browse the repository at this point in the history
)

Output from acceptance testing in AWS Commercial:

```
--- PASS: TestAccAWSIAMInstanceProfile_withoutRole (24.71s)
--- PASS: TestAccAWSIAMInstanceProfile_disappears_role (25.87s)
--- PASS: TestAccAWSIAMInstanceProfile_disappears (26.09s)
--- PASS: TestAccAWSIAMInstanceProfile_namePrefix (28.30s)
--- PASS: TestAccAWSIAMInstanceProfile_basic (31.35s)
--- PASS: TestAccAWSIAMInstanceProfile_tags (44.67s)
```

Output from acceptance testing in AWS GovCloud (US):

```
--- PASS: TestAccAWSIAMInstanceProfile_withoutRole (32.70s)
--- PASS: TestAccAWSIAMInstanceProfile_disappears_role (34.17s)
--- PASS: TestAccAWSIAMInstanceProfile_disappears (34.40s)
--- PASS: TestAccAWSIAMInstanceProfile_basic (39.09s)
--- PASS: TestAccAWSIAMInstanceProfile_namePrefix (41.02s)
--- PASS: TestAccAWSIAMInstanceProfile_tags (69.02s)
```
  • Loading branch information
DrFaust92 authored Mar 25, 2021
1 parent a01547a commit 82cc1be
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 27 deletions.
3 changes: 3 additions & 0 deletions .changelog/17962.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_iam_instance_profile: Add tagging support
```
35 changes: 35 additions & 0 deletions aws/internal/keyvaluetags/iam_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,41 @@ func IamUserUpdateTags(conn *iam.IAM, identifier string, oldTagsMap interface{},
return nil
}

// IamInstanceProfileUpdateTags updates IAM Instance Profile tags.
// The identifier is the Instance Profile name.
func IamInstanceProfileUpdateTags(conn *iam.IAM, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error {
oldTags := New(oldTagsMap)
newTags := New(newTagsMap)

if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 {
input := &iam.UntagInstanceProfileInput{
InstanceProfileName: aws.String(identifier),
TagKeys: aws.StringSlice(removedTags.Keys()),
}

_, err := conn.UntagInstanceProfile(input)

if err != nil {
return fmt.Errorf("error untagging resource (%s): %w", identifier, err)
}
}

if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 {
input := &iam.TagInstanceProfileInput{
InstanceProfileName: aws.String(identifier),
Tags: updatedTags.IgnoreAws().IamTags(),
}

_, err := conn.TagInstanceProfile(input)

if err != nil {
return fmt.Errorf("error tagging resource (%s): %w", identifier, err)
}
}

return nil
}

// IamOpenIDConnectProviderUpdateTags updates IAM OpenID Connect Provider tags.
// The identifier is the OpenID Connect Provider ARN.
func IamOpenIDConnectProviderUpdateTags(conn *iam.IAM, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error {
Expand Down
23 changes: 20 additions & 3 deletions aws/resource_aws_iam_instance_profile.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"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
)

func resourceAwsIamInstanceProfile() *schema.Resource {
Expand Down Expand Up @@ -67,6 +68,7 @@ func resourceAwsIamInstanceProfile() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"tags": tagsSchema(),
},
}
}
Expand All @@ -86,12 +88,13 @@ func resourceAwsIamInstanceProfileCreate(d *schema.ResourceData, meta interface{
request := &iam.CreateInstanceProfileInput{
InstanceProfileName: aws.String(name),
Path: aws.String(d.Get("path").(string)),
Tags: keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().IamTags(),
}

var err error
response, err := conn.CreateInstanceProfile(request)
if err == nil {
err = instanceProfileReadResult(d, response.InstanceProfile)
err = instanceProfileReadResult(d, response.InstanceProfile, meta)
}
if err != nil {
return fmt.Errorf("creating IAM instance profile %s: %w", name, err)
Expand Down Expand Up @@ -186,6 +189,14 @@ func resourceAwsIamInstanceProfileUpdate(d *schema.ResourceData, meta interface{
}
}

if d.HasChange("tags") {
o, n := d.GetChange("tags")

if err := keyvaluetags.IamInstanceProfileUpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating tags for IAM Instance Profile (%s): %w", d.Id(), err)
}
}

return nil
}

Expand Down Expand Up @@ -225,7 +236,7 @@ func resourceAwsIamInstanceProfileRead(d *schema.ResourceData, meta interface{})
}
}

return instanceProfileReadResult(d, instanceProfile)
return instanceProfileReadResult(d, instanceProfile, meta)
}

func resourceAwsIamInstanceProfileDelete(d *schema.ResourceData, meta interface{}) error {
Expand All @@ -249,7 +260,9 @@ func resourceAwsIamInstanceProfileDelete(d *schema.ResourceData, meta interface{
return nil
}

func instanceProfileReadResult(d *schema.ResourceData, result *iam.InstanceProfile) error {
func instanceProfileReadResult(d *schema.ResourceData, result *iam.InstanceProfile, meta interface{}) error {
ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig

d.SetId(aws.StringValue(result.InstanceProfileName))
if err := d.Set("name", result.InstanceProfileName); err != nil {
return err
Expand All @@ -269,5 +282,9 @@ func instanceProfileReadResult(d *schema.ResourceData, result *iam.InstanceProfi
d.Set("role", result.Roles[0].RoleName) //there will only be 1 role returned
}

if err := d.Set("tags", keyvaluetags.IamKeyValueTags(result.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
}

return nil
}
152 changes: 128 additions & 24 deletions aws/resource_aws_iam_instance_profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,65 @@ package aws

import (
"fmt"
"log"
"strings"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func init() {
resource.AddTestSweepers("aws_iam_instance_profile", &resource.Sweeper{
Name: "aws_iam_instance_profile",
F: testSweepIamInstanceProfile,
Dependencies: []string{"aws_iam_role"},
})
}

func testSweepIamInstanceProfile(region string) error {
client, err := sharedClientForRegion(region)
if err != nil {
return fmt.Errorf("error getting client: %w", err)
}
conn := client.(*AWSClient).iamconn

var sweeperErrs *multierror.Error

out, err := conn.ListInstanceProfiles(&iam.ListInstanceProfilesInput{})

for _, instanceProfile := range out.InstanceProfiles {
name := aws.StringValue(instanceProfile.InstanceProfileName)

r := resourceAwsIamInstanceProfile()
d := r.Data(nil)
d.SetId(name)
err := r.Delete(d, client)

if err != nil {
sweeperErr := fmt.Errorf("error deleting IAM Instance Profile (%s): %w", name, err)
log.Printf("[ERROR] %s", sweeperErr)
sweeperErrs = multierror.Append(sweeperErrs, sweeperErr)
continue
}
}

if testSweepSkipSweepError(err) {
log.Printf("[WARN] Skipping IAM Instance Profile sweep for %s: %s", region, err)
return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors
}

if err != nil {
sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error describing IAM Instance Profiles: %w", err))
}

return sweeperErrs.ErrorOrNil()
}

func TestAccAWSIAMInstanceProfile_basic(t *testing.T) {
var conf iam.GetInstanceProfileOutput
resourceName := "aws_iam_instance_profile.test"
Expand All @@ -30,6 +79,7 @@ func TestAccAWSIAMInstanceProfile_basic(t *testing.T) {
testAccCheckResourceAttrGlobalARN(resourceName, "arn", "iam", fmt.Sprintf("instance-profile/test-%s", rName)),
resource.TestCheckResourceAttrPair(resourceName, "role", "aws_iam_role.test", "name"),
resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("test-%s", rName)),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
),
},
{
Expand Down Expand Up @@ -67,6 +117,52 @@ func TestAccAWSIAMInstanceProfile_withoutRole(t *testing.T) {
})
}

func TestAccAWSIAMInstanceProfile_tags(t *testing.T) {
var conf iam.GetInstanceProfileOutput
resourceName := "aws_iam_instance_profile.test"
rName := acctest.RandString(5)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, iam.EndpointsID),
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSInstanceProfileDestroy,
Steps: []resource.TestStep{
{
Config: testAccAwsIamInstanceProfileConfigTags1(rName, "key1", "value1"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSInstanceProfileExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"name_prefix"},
},
{
Config: testAccAwsIamInstanceProfileConfigTags2(rName, "key1", "value1updated", "key2", "value2"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSInstanceProfileExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "tags.%", "2"),
resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"),
resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
),
},
{
Config: testAccAwsIamInstanceProfileConfigTags1(rName, "key2", "value2"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSInstanceProfileExists(resourceName, &conf),
resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
),
},
},
})
}

func TestAccAWSIAMInstanceProfile_namePrefix(t *testing.T) {
var conf iam.GetInstanceProfileOutput
rName := acctest.RandString(5)
Expand Down Expand Up @@ -213,7 +309,7 @@ func testAccCheckAWSInstanceProfileExists(n string, res *iam.GetInstanceProfileO
}
}

func testAccAwsIamInstanceProfileConfig(rName string) string {
func testAccAwsIamInstanceProfileBaseConfig(rName string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "test" {
name = "test-%s"
Expand All @@ -237,7 +333,11 @@ resource "aws_iam_role" "test" {
}
EOF
}
`, rName)
}

func testAccAwsIamInstanceProfileConfig(rName string) string {
return testAccAwsIamInstanceProfileBaseConfig(rName) + fmt.Sprintf(`
resource "aws_iam_instance_profile" "test" {
name = "test-%[1]s"
role = aws_iam_role.test.name
Expand All @@ -254,33 +354,37 @@ resource "aws_iam_instance_profile" "test" {
}

func testAccAWSInstanceProfilePrefixNameConfig(rName string) string {
return fmt.Sprintf(`
resource "aws_iam_role" "test" {
name = "test-%s"
return testAccAwsIamInstanceProfileBaseConfig(rName) + `
resource "aws_iam_instance_profile" "test" {
name_prefix = "test-"
role = aws_iam_role.test.name
}
`
}

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"ec2.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
func testAccAwsIamInstanceProfileConfigTags1(rName, tagKey1, tagValue1 string) string {
return testAccAwsIamInstanceProfileBaseConfig(rName) + fmt.Sprintf(`
resource "aws_iam_instance_profile" "test" {
name = "test-%[1]s"
role = aws_iam_role.test.name
tags = {
%[2]q = %[3]q
}
}
EOF
`, rName, tagKey1, tagValue1)
}

func testAccAwsIamInstanceProfileConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string {
return testAccAwsIamInstanceProfileBaseConfig(rName) + fmt.Sprintf(`
resource "aws_iam_instance_profile" "test" {
name_prefix = "test-"
role = aws_iam_role.test.name
name = "test-%[1]s"
role = aws_iam_role.test.name
tags = {
%[2]q = %[3]q
%[4]q = %[5]q
}
}
`, rName)
`, rName, tagKey1, tagValue1, tagKey2, tagValue2)
}
1 change: 1 addition & 0 deletions website/docs/r/iam_instance_profile.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ The following arguments are optional:
* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`.
* `path` - (Optional, default "/") Path to the instance profile. For more information about paths, see [IAM Identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html) in the IAM User Guide. Can be a string of characters consisting of either a forward slash (`/`) by itself or a string that must begin and end with forward slashes. Can include any ASCII character from the ! (\u0021) through the DEL character (\u007F), including most punctuation characters, digits, and upper and lowercase letters.
* `role` - (Optional) Name of the role to add to the profile.
* `tags` - (Optional) Map of resource tags for the IAM Instance Profile.

## Attributes Reference

Expand Down

0 comments on commit 82cc1be

Please sign in to comment.