Skip to content

Commit

Permalink
Merge pull request #22544 from hashicorp/f-iso-tagging-iam
Browse files Browse the repository at this point in the history
iam: ISO-friendly tagging
  • Loading branch information
YakDriver committed Jan 12, 2022
2 parents 5e53e6f + 7ec34ac commit 4896b83
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 36 deletions.
15 changes: 15 additions & 0 deletions .changelog/22544.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```release-note:enhancement
resource/aws_iam_role: Attempt `tags`-on-create, fallback to tag after create, and allow some `tags` errors to be non-fatal to support non-standard AWS partitions (i.e., ISO)
```

```release-note:enhancement
data-source/aws_iam_role: Allow some `tags` errors to be non-fatal to support non-standard AWS partitions (i.e., ISO)
```

```release-note:enhancement
resource/aws_iam_user: Attempt `tags`-on-create, fallback to tag after create, and allow some `tags` errors to be non-fatal to support non-standard AWS partitions (i.e., ISO)
```

```release-note:enhancement
data-source/aws_iam_user: Allow some `tags` errors to be non-fatal to support non-standard AWS partitions (i.e., ISO)
```
5 changes: 5 additions & 0 deletions internal/service/iam/consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package iam

const (
ErrCodeAccessDenied = "AccessDenied"
)
118 changes: 86 additions & 32 deletions internal/service/iam/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
awspolicy "github.com/hashicorp/awspolicyequivalence"
Expand Down Expand Up @@ -199,25 +200,21 @@ func resourceRoleCreate(d *schema.ResourceData, meta interface{}) error {
request.Tags = Tags(tags.IgnoreAWS())
}

outputRaw, err := tfresource.RetryWhen(
PropagationTimeout,
func() (interface{}, error) {
return conn.CreateRole(request)
},
func(err error) (bool, error) {
if tfawserr.ErrMessageContains(err, iam.ErrCodeMalformedPolicyDocumentException, "Invalid principal in policy") {
return true, err
}
output, err := retryCreateRole(conn, request)

return false, err
},
)
// Some partitions (i.e., ISO) may not support tag-on-create
if request.Tags != nil && meta.(*conns.AWSClient).Partition != endpoints.AwsPartitionID && (tfawserr.ErrCodeContains(err, ErrCodeAccessDenied) || tfawserr.ErrCodeContains(err, iam.ErrCodeInvalidInputException) || tfawserr.ErrCodeContains(err, iam.ErrCodeServiceFailureException)) {
log.Printf("[WARN] IAM Role (%s) create failed (%s) with tags. Trying create without tags.", d.Id(), err)
request.Tags = nil

output, err = retryCreateRole(conn, request)
}

if err != nil {
return fmt.Errorf("error creating IAM Role (%s): %w", name, err)
}

roleName := aws.StringValue(outputRaw.(*iam.CreateRoleOutput).Role.RoleName)
roleName := aws.StringValue(output.Role.RoleName)

if v, ok := d.GetOk("inline_policy"); ok && v.(*schema.Set).Len() > 0 {
policies := expandRoleInlinePolicies(roleName, v.(*schema.Set).List())
Expand All @@ -234,6 +231,22 @@ func resourceRoleCreate(d *schema.ResourceData, meta interface{}) error {
}

d.SetId(roleName)

// Some partitions (i.e., ISO) may not support tag-on-create, attempt tag after create
if request.Tags == nil && len(tags) > 0 && meta.(*conns.AWSClient).Partition != endpoints.AwsPartitionID {
err := roleUpdateTags(conn, d.Id(), nil, tags)

// If default tags only, log and continue. Otherwise, error.
if v, ok := d.GetOk("tags"); (!ok || len(v.(map[string]interface{})) == 0) && (tfawserr.ErrCodeContains(err, ErrCodeAccessDenied) || tfawserr.ErrCodeContains(err, iam.ErrCodeInvalidInputException) || tfawserr.ErrCodeContains(err, iam.ErrCodeServiceFailureException)) {
log.Printf("[WARN] error adding tags after create for IAM Role (%s): %s", d.Id(), err)
return resourceRoleRead(d, meta)
}

if err != nil {
return fmt.Errorf("error creating IAM Role (%s) tags: %w", d.Id(), err)
}
}

return resourceRoleRead(d, meta)
}

Expand Down Expand Up @@ -277,17 +290,6 @@ func resourceRoleRead(d *schema.ResourceData, meta interface{}) error {
}
d.Set("unique_id", role.RoleId)

tags := KeyValueTags(role.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
}

if err := d.Set("tags_all", tags.Map()); err != nil {
return fmt.Errorf("error setting tags_all: %w", err)
}

assumeRolePolicy, err := url.QueryUnescape(*role.AssumeRolePolicyDocument)
if err != nil {
return err
Expand Down Expand Up @@ -318,6 +320,23 @@ func resourceRoleRead(d *schema.ResourceData, meta interface{}) error {
}
d.Set("managed_policy_arns", managedPolicies)

tags := KeyValueTags(role.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

// Some partitions (i.e., ISO) may not support tagging, giving error
if meta.(*conns.AWSClient).Partition != endpoints.AwsPartitionID && (tfawserr.ErrCodeContains(err, ErrCodeAccessDenied) || tfawserr.ErrCodeContains(err, iam.ErrCodeInvalidInputException) || tfawserr.ErrCodeContains(err, iam.ErrCodeServiceFailureException)) {
log.Printf("[WARN] Unable to list tags for IAM Role %s: %s", d.Id(), err)
return nil
}

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
}

if err := d.Set("tags_all", tags.Map()); err != nil {
return fmt.Errorf("error setting tags_all: %w", err)
}

return nil
}

Expand Down Expand Up @@ -401,14 +420,6 @@ func resourceRoleUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

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

if err := roleUpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating IAM Role (%s) tags: %s", d.Id(), err)
}
}

if d.HasChange("inline_policy") && inlinePoliciesActualDiff(d) {
roleName := d.Get("name").(string)

Expand Down Expand Up @@ -475,6 +486,22 @@ func resourceRoleUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

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

err := roleUpdateTags(conn, d.Id(), o, n)

// Some partitions may not support tagging, giving error
if meta.(*conns.AWSClient).Partition != endpoints.AwsPartitionID && (tfawserr.ErrCodeContains(err, ErrCodeAccessDenied) || tfawserr.ErrCodeContains(err, iam.ErrCodeInvalidInputException) || tfawserr.ErrCodeContains(err, iam.ErrCodeServiceFailureException)) {
log.Printf("[WARN] Unable to update tags for IAM Role %s: %s", d.Id(), err)
return resourceRoleRead(d, meta)
}

if err != nil {
return fmt.Errorf("error updating IAM Role (%s) tags: %w", d.Id(), err)
}
}

return resourceRoleRead(d, meta)
}

Expand Down Expand Up @@ -582,6 +609,33 @@ func deleteRoleInstanceProfiles(conn *iam.IAM, roleName string) error {
return nil
}

func retryCreateRole(conn *iam.IAM, input *iam.CreateRoleInput) (*iam.CreateRoleOutput, error) {
outputRaw, err := tfresource.RetryWhen(
PropagationTimeout,
func() (interface{}, error) {
return conn.CreateRole(input)
},
func(err error) (bool, error) {
if tfawserr.ErrMessageContains(err, iam.ErrCodeMalformedPolicyDocumentException, "Invalid principal in policy") {
return true, err
}

return false, err
},
)

if err != nil {
return nil, err
}

output, ok := outputRaw.(*iam.CreateRoleOutput)
if !ok || output == nil || aws.StringValue(output.Role.RoleName) == "" {
return nil, fmt.Errorf("create IAM role (%s) returned an empty result", aws.StringValue(input.RoleName))
}

return output, err
}

func readRolePolicyAttachments(conn *iam.IAM, roleName string) ([]*string, error) {
managedPolicies := make([]*string, 0)
input := &iam.ListAttachedRolePoliciesInput{
Expand Down
17 changes: 16 additions & 1 deletion internal/service/iam/role_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package iam

import (
"fmt"
"log"
"net/url"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
Expand Down Expand Up @@ -86,7 +89,6 @@ func dataSourceRoleRead(d *schema.ResourceData, meta interface{}) error {
d.Set("permissions_boundary", output.Role.PermissionsBoundary.PermissionsBoundaryArn)
}
d.Set("unique_id", output.Role.RoleId)
d.Set("tags", KeyValueTags(output.Role.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map())

assumRolePolicy, err := url.QueryUnescape(aws.StringValue(output.Role.AssumeRolePolicyDocument))
if err != nil {
Expand All @@ -96,6 +98,19 @@ func dataSourceRoleRead(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("error setting assume_role_policy: %w", err)
}

tags := KeyValueTags(output.Role.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

// Some partitions (i.e., ISO) may not support tagging, giving error
if meta.(*conns.AWSClient).Partition != endpoints.AwsPartitionID && (tfawserr.ErrCodeContains(err, ErrCodeAccessDenied) || tfawserr.ErrCodeContains(err, iam.ErrCodeInvalidInputException) || tfawserr.ErrCodeContains(err, iam.ErrCodeServiceFailureException)) {
log.Printf("[WARN] Unable to list tags for IAM Role %s: %s", d.Id(), err)
return nil
}

//lintignore:AWSR002
if err := d.Set("tags", tags.Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
}

d.SetId(name)

return nil
Expand Down
43 changes: 41 additions & 2 deletions internal/service/iam/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"regexp"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
Expand Down Expand Up @@ -98,12 +99,36 @@ func resourceUserCreate(d *schema.ResourceData, meta interface{}) error {

log.Println("[DEBUG] Create IAM User request:", request)
createResp, err := conn.CreateUser(request)

// Some partitions (i.e., ISO) may not support tag-on-create
if request.Tags != nil && meta.(*conns.AWSClient).Partition != endpoints.AwsPartitionID && (tfawserr.ErrCodeContains(err, ErrCodeAccessDenied) || tfawserr.ErrCodeContains(err, iam.ErrCodeInvalidInputException) || tfawserr.ErrCodeContains(err, iam.ErrCodeServiceFailureException)) {
log.Printf("[WARN] IAM User (%s) create failed (%s) with tags. Trying create without tags.", d.Get("name").(string), err)
request.Tags = nil

createResp, err = conn.CreateUser(request)
}

if err != nil {
return fmt.Errorf("Error creating IAM User %s: %s", name, err)
}

d.SetId(aws.StringValue(createResp.User.UserName))

// Some partitions (i.e., ISO) may not support tag-on-create, attempt tag after create
if request.Tags == nil && len(tags) > 0 && meta.(*conns.AWSClient).Partition != endpoints.AwsPartitionID {
err := userUpdateTags(conn, d.Id(), nil, tags)

// If default tags only, log and continue. Otherwise, error.
if v, ok := d.GetOk("tags"); (!ok || len(v.(map[string]interface{})) == 0) && (tfawserr.ErrCodeContains(err, ErrCodeAccessDenied) || tfawserr.ErrCodeContains(err, iam.ErrCodeInvalidInputException) || tfawserr.ErrCodeContains(err, iam.ErrCodeServiceFailureException)) {
log.Printf("[WARN] error adding tags after create for IAM User (%s): %s", d.Id(), err)
return resourceUserRead(d, meta)
}

if err != nil {
return fmt.Errorf("error creating IAM User (%s) tags: %w", d.Id(), err)
}
}

return resourceUserRead(d, meta)
}

Expand Down Expand Up @@ -162,6 +187,12 @@ func resourceUserRead(d *schema.ResourceData, meta interface{}) error {

tags := KeyValueTags(output.User.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

// Some partitions (i.e., ISO) may not support tagging, giving error
if meta.(*conns.AWSClient).Partition != endpoints.AwsPartitionID && (tfawserr.ErrCodeContains(err, ErrCodeAccessDenied) || tfawserr.ErrCodeContains(err, iam.ErrCodeInvalidInputException) || tfawserr.ErrCodeContains(err, iam.ErrCodeServiceFailureException)) {
log.Printf("[WARN] Unable to list tags for IAM User %s: %s", d.Id(), err)
return nil
}

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
Expand Down Expand Up @@ -226,8 +257,16 @@ func resourceUserUpdate(d *schema.ResourceData, meta interface{}) error {
if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")

if err := userUpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating IAM User (%s) tags: %s", d.Id(), err)
err := userUpdateTags(conn, d.Id(), o, n)

// Some partitions may not support tagging, giving error
if meta.(*conns.AWSClient).Partition != endpoints.AwsPartitionID && (tfawserr.ErrCodeContains(err, ErrCodeAccessDenied) || tfawserr.ErrCodeContains(err, iam.ErrCodeInvalidInputException) || tfawserr.ErrCodeContains(err, iam.ErrCodeServiceFailureException)) {
log.Printf("[WARN] Unable to update tags for IAM User %s: %s", d.Id(), err)
return resourceUserRead(d, meta)
}

if err != nil {
return fmt.Errorf("error updating IAM User (%s) tags: %w", d.Id(), err)
}
}

Expand Down
14 changes: 13 additions & 1 deletion internal/service/iam/user_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
Expand Down Expand Up @@ -65,7 +67,17 @@ func dataSourceUserRead(d *schema.ResourceData, meta interface{}) error {
d.Set("permissions_boundary", user.PermissionsBoundary.PermissionsBoundaryArn)
}
d.Set("user_id", user.UserId)
if err := d.Set("tags", KeyValueTags(user.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {

tags := KeyValueTags(user.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

// Some partitions (i.e., ISO) may not support tagging, giving error
if meta.(*conns.AWSClient).Partition != endpoints.AwsPartitionID && (tfawserr.ErrCodeContains(err, ErrCodeAccessDenied) || tfawserr.ErrCodeContains(err, iam.ErrCodeInvalidInputException) || tfawserr.ErrCodeContains(err, iam.ErrCodeServiceFailureException)) {
log.Printf("[WARN] Unable to list tags for IAM User %s: %s", d.Id(), err)
return nil
}

//lintignore:AWSR002
if err := d.Set("tags", tags.Map()); err != nil {
return fmt.Errorf("error setting tags: %w", err)
}

Expand Down

0 comments on commit 4896b83

Please sign in to comment.