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

service/iam: Handle read-after-create eventual consistency in IAM Role resources #18435

Merged
merged 2 commits into from
Apr 1, 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
11 changes: 11 additions & 0 deletions .changelog/18435.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:bug
resource/aws_iam_role: Handle read-after-create eventual consistency
```

```release-note:bug
resource/aws_iam_role_policy: Handle read-after-create eventual consistency
```

```release-note:bug
resource/aws_iam_role_policy_attachment: Handle read-after-create eventual consistency
```
39 changes: 30 additions & 9 deletions aws/resource_aws_iam_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func resourceAwsIamRole() *schema.Resource {
Expand Down Expand Up @@ -235,22 +236,42 @@ func resourceAwsIamRoleRead(d *schema.ResourceData, meta interface{}) error {
RoleName: aws.String(d.Id()),
}

getResp, err := iamconn.GetRole(request)
if err != nil {
if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
log.Printf("[WARN] IAM Role %q not found, removing from state", d.Id())
d.SetId("")
return nil
var getResp *iam.GetRoleOutput

err := resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError {
var err error

getResp, err = iamconn.GetRole(request)

if d.IsNewResource() && tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) {
return resource.RetryableError(err)
}

if err != nil {
return resource.NonRetryableError(err)
}
return fmt.Errorf("Error reading IAM Role %s: %s", d.Id(), err)

return nil
})

if tfresource.TimedOut(err) {
getResp, err = iamconn.GetRole(request)
}

if getResp == nil || getResp.Role == nil {
log.Printf("[WARN] IAM Role %q not found, removing from state", d.Id())
if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) {
log.Printf("[WARN] IAM Role (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

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

if getResp == nil || getResp.Role == nil {
return fmt.Errorf("error reading IAM Role (%s): empty response", d.Id())
}

role := getResp.Role

d.Set("arn", role.Arn)
Expand Down
43 changes: 34 additions & 9 deletions aws/resource_aws_iam_role_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import (

"github.com/aws/aws-sdk-go/aws"
"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"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func resourceAwsIamRolePolicy() *schema.Resource {
Expand Down Expand Up @@ -94,18 +97,40 @@ func resourceAwsIamRolePolicyRead(d *schema.ResourceData, meta interface{}) erro
RoleName: aws.String(role),
}

getResp, err := iamconn.GetRolePolicy(request)
if err != nil {
if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
log.Printf("[WARN] IAM Role Policy (%s) for %s not found, removing from state", name, role)
d.SetId("")
return nil
var getResp *iam.GetRolePolicyOutput

err = resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError {
var err error

getResp, err = iamconn.GetRolePolicy(request)

if d.IsNewResource() && tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) {
return resource.RetryableError(err)
}

if err != nil {
return resource.NonRetryableError(err)
}
return fmt.Errorf("Error reading IAM policy %s from role %s: %s", name, role, err)

return nil
})

if tfresource.TimedOut(err) {
getResp, err = iamconn.GetRolePolicy(request)
}

if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) {
log.Printf("[WARN] IAM Role Policy (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

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

if getResp.PolicyDocument == nil {
return fmt.Errorf("GetRolePolicy returned a nil policy document")
if getResp == nil || getResp.PolicyDocument == nil {
return fmt.Errorf("error reading IAM Role Policy (%s): empty response", d.Id())
}

policy, err := url.QueryUnescape(*getResp.PolicyDocument)
Expand Down
43 changes: 37 additions & 6 deletions aws/resource_aws_iam_role_policy_attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import (

"github.com/aws/aws-sdk-go/aws"
"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"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/iam/waiter"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func resourceAwsIamRolePolicyAttachment() *schema.Resource {
Expand Down Expand Up @@ -56,21 +59,49 @@ func resourceAwsIamRolePolicyAttachmentRead(d *schema.ResourceData, meta interfa
conn := meta.(*AWSClient).iamconn
role := d.Get("role").(string)
policyARN := d.Get("policy_arn").(string)
// Human friendly ID for error messages since d.Id() is non-descriptive
id := fmt.Sprintf("%s:%s", role, policyARN)

hasPolicyAttachment, err := iamRoleHasPolicyARNAttachment(conn, role, policyARN)
var hasPolicyAttachment bool

if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
log.Printf("[WARN] IAM Role (%s) not found, removing from state", role)
err := resource.Retry(waiter.PropagationTimeout, func() *resource.RetryError {
var err error

hasPolicyAttachment, err = iamRoleHasPolicyARNAttachment(conn, role, policyARN)

if d.IsNewResource() && tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) {
return resource.RetryableError(err)
}

if err != nil {
return resource.NonRetryableError(err)
}

if d.IsNewResource() && !hasPolicyAttachment {
return resource.RetryableError(&resource.NotFoundError{
LastError: fmt.Errorf("IAM Role Managed Policy Attachment (%s) not found", id),
})
}

return nil
})

if tfresource.TimedOut(err) {
hasPolicyAttachment, err = iamRoleHasPolicyARNAttachment(conn, role, policyARN)
}

if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) {
log.Printf("[WARN] IAM Role Managed Policy Attachment (%s) not found, removing from state", id)
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error finding IAM Role (%s) Policy Attachment (%s): %s", role, policyARN, err)
return fmt.Errorf("error reading IAM Role Managed Policy Attachment (%s): %w", id, err)
}

if !hasPolicyAttachment {
log.Printf("[WARN] IAM Role (%s) Policy Attachment (%s) not found, removing from state", role, policyARN)
if !d.IsNewResource() && !hasPolicyAttachment {
log.Printf("[WARN] IAM Role Managed Policy Attachment (%s) not found, removing from state", id)
d.SetId("")
return nil
}
Expand Down