Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r/iam_instance_profile - add tagging support to instance profile + sweeper #17962

Merged
merged 8 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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