Skip to content

Commit

Permalink
service/ecr: Handle read-after-create eventual consistency in resources
Browse files Browse the repository at this point in the history
Reference: #7722
Reference: #9987
Reference: #16796

Output from acceptance testing in AWS Commercial:

```
--- PASS: TestAccAWSEcrLifecyclePolicy_basic (27.52s)

--- PASS: TestAccAWSEcrRepository_basic (26.60s)
--- PASS: TestAccAWSEcrRepository_encryption_aes256 (44.04s)
--- PASS: TestAccAWSEcrRepository_encryption_kms (45.31s)
--- PASS: TestAccAWSEcrRepository_image_scanning_configuration (54.50s)
--- PASS: TestAccAWSEcrRepository_immutability (26.96s)
--- PASS: TestAccAWSEcrRepository_tags (37.62s)

--- PASS: TestAccAWSEcrRepositoryPolicy_basic (39.48s)
--- PASS: TestAccAWSEcrRepositoryPolicy_disappears (22.65s)
--- PASS: TestAccAWSEcrRepositoryPolicy_disappears_repository (20.02s)
--- PASS: TestAccAWSEcrRepositoryPolicy_iam (34.80s)
```

Output from acceptance testing in AWS GovCloud (US):

```
--- PASS: TestAccAWSEcrLifecyclePolicy_basic (34.33s)

--- PASS: TestAccAWSEcrRepository_basic (31.52s)
--- PASS: TestAccAWSEcrRepository_encryption_aes256 (71.67s)
--- PASS: TestAccAWSEcrRepository_encryption_kms (62.87s)
--- PASS: TestAccAWSEcrRepository_image_scanning_configuration (80.53s)
--- PASS: TestAccAWSEcrRepository_immutability (35.40s)
--- PASS: TestAccAWSEcrRepository_tags (60.99s)

--- PASS: TestAccAWSEcrRepositoryPolicy_basic (59.75s)
--- PASS: TestAccAWSEcrRepositoryPolicy_disappears (30.05s)
--- PASS: TestAccAWSEcrRepositoryPolicy_disappears_repository (31.80s)
--- PASS: TestAccAWSEcrRepositoryPolicy_iam (38.94s)
```
  • Loading branch information
bflad committed Mar 29, 2021
1 parent 19b8055 commit 874a103
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 24 deletions.
11 changes: 11 additions & 0 deletions .changelog/pending.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:bug
resource/aws_ecr_lifecycle_policy: Handle read-after-create eventual consistency
```

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

```release-note:bug
resource/aws_ecr_repository_policy: Handle read-after-create eventual consistency
```
10 changes: 10 additions & 0 deletions aws/internal/service/ecr/waiter/waiter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package waiter

import (
"time"
)

const (
// Maximum amount of time to wait for ECR changes to propagate
PropagationTimeout = 2 * time.Minute
)
53 changes: 44 additions & 9 deletions aws/resource_aws_ecr_lifecycle_policy.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package aws

import (
"fmt"
"log"

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

func resourceAwsEcrLifecyclePolicy() *schema.Resource {
Expand Down Expand Up @@ -62,17 +68,46 @@ func resourceAwsEcrLifecyclePolicyRead(d *schema.ResourceData, meta interface{})
RepositoryName: aws.String(d.Id()),
}

resp, err := conn.GetLifecyclePolicy(input)
if err != nil {
if isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
d.SetId("")
return nil
var resp *ecr.GetLifecyclePolicyOutput

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

resp, err = conn.GetLifecyclePolicy(input)

if d.IsNewResource() && tfawserr.ErrCodeEquals(err, ecr.ErrCodeLifecyclePolicyNotFoundException) {
return resource.RetryableError(err)
}
if isAWSErr(err, ecr.ErrCodeLifecyclePolicyNotFoundException, "") {
d.SetId("")
return nil

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

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

return nil
})

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

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

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

if resp == nil {
return fmt.Errorf("error reading ECR Lifecycle Policy (%s): empty response", d.Id())
}

d.Set("repository", resp.RepositoryName)
Expand Down
23 changes: 17 additions & 6 deletions aws/resource_aws_ecr_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import (

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ecr"
"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/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/ecr/waiter"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func resourceAwsEcrRepository() *schema.Resource {
Expand Down Expand Up @@ -145,30 +148,38 @@ func resourceAwsEcrRepositoryRead(d *schema.ResourceData, meta interface{}) erro
RepositoryNames: aws.StringSlice([]string{d.Id()}),
}

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

out, err = conn.DescribeRepositories(input)
if d.IsNewResource() && isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {

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

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

return nil
})

if isResourceTimeoutError(err) {
if tfresource.TimedOut(err) {
out, err = conn.DescribeRepositories(input)
}

if isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") {
if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, ecr.ErrCodeRepositoryNotFoundException) {
log.Printf("[WARN] ECR Repository (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

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

if out == nil || len(out.Repositories) == 0 || out.Repositories[0] == nil {
return fmt.Errorf("error reading ECR Repository (%s): empty response", d.Id())
}

repository := out.Repositories[0]
Expand Down
57 changes: 48 additions & 9 deletions aws/resource_aws_ecr_repository_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import (

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

func resourceAwsEcrRepositoryPolicy() *schema.Resource {
Expand Down Expand Up @@ -83,18 +86,54 @@ func resourceAwsEcrRepositoryPolicyPut(d *schema.ResourceData, meta interface{})
func resourceAwsEcrRepositoryPolicyRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ecrconn

log.Printf("[DEBUG] Reading repository policy %s", d.Id())
out, err := conn.GetRepositoryPolicy(&ecr.GetRepositoryPolicyInput{
input := &ecr.GetRepositoryPolicyInput{
RepositoryName: aws.String(d.Id()),
}

var out *ecr.GetRepositoryPolicyOutput

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

out, err = conn.GetRepositoryPolicy(input)

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

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

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

return nil
})

if tfresource.TimedOut(err) {
out, err = conn.GetRepositoryPolicy(input)
}

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

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

if err != nil {
if isAWSErr(err, ecr.ErrCodeRepositoryNotFoundException, "") ||
isAWSErr(err, ecr.ErrCodeRepositoryPolicyNotFoundException, "") {
log.Printf("[WARN] ECR Repository Policy %s not found, removing", d.Id())
d.SetId("")
return nil
}
return err
return fmt.Errorf("error reading ECR Repository Policy (%s): %w", d.Id(), err)
}

if out == nil {
return fmt.Errorf("error reading ECR Repository Policy (%s): empty response", d.Id())
}

log.Printf("[DEBUG] Received repository policy %s", out)
Expand Down

0 comments on commit 874a103

Please sign in to comment.