From 11ec6722e4ae38a2baaa98412adb9653c8a59614 Mon Sep 17 00:00:00 2001 From: Andrew Titmuss Date: Wed, 4 May 2022 23:12:02 +1000 Subject: [PATCH 1/2] fix PreconditionFailed errors on aws_cloudfront_distribution --- internal/service/cloudfront/distribution.go | 23 + .../service/cloudfront/distribution_test.go | 474 ++++++++++++++++++ 2 files changed, 497 insertions(+) diff --git a/internal/service/cloudfront/distribution.go b/internal/service/cloudfront/distribution.go index 2b943ac1b80..899cae2e29b 100644 --- a/internal/service/cloudfront/distribution.go +++ b/internal/service/cloudfront/distribution.go @@ -962,6 +962,29 @@ func resourceDistributionUpdate(d *schema.ResourceData, meta interface{}) error return nil }) + // Refresh our ETag if it is out of date and attempt update again + if tfawserr.ErrCodeEquals(err, cloudfront.ErrCodePreconditionFailed) { + getDistributionInput := &cloudfront.GetDistributionInput{ + Id: aws.String(d.Id()), + } + var getDistributionOutput *cloudfront.GetDistributionOutput + + log.Printf("[DEBUG] Refreshing CloudFront Distribution (%s) ETag", d.Id()) + getDistributionOutput, err = conn.GetDistribution(getDistributionInput) + + if err != nil { + return fmt.Errorf("error refreshing CloudFront Distribution (%s) ETag: %s", d.Id(), err) + } + + if getDistributionOutput == nil { + return fmt.Errorf("error refreshing CloudFront Distribution (%s) ETag: empty response", d.Id()) + } + + params.IfMatch = getDistributionOutput.ETag + + _, err = conn.UpdateDistribution(params) + } + // Propagate AWS Go SDK retried error, if any if tfresource.TimedOut(err) { _, err = conn.UpdateDistribution(params) diff --git a/internal/service/cloudfront/distribution_test.go b/internal/service/cloudfront/distribution_test.go index e6f6a0072e5..dc913ae8537 100644 --- a/internal/service/cloudfront/distribution_test.go +++ b/internal/service/cloudfront/distribution_test.go @@ -1272,6 +1272,78 @@ func TestAccCloudFrontDistribution_waitForDeployment(t *testing.T) { }) } +func TestAccCloudFrontDistribution_preconditionFailed(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var distribution cloudfront.Distribution + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudfront_distribution.main" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(cloudfront.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, cloudfront.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckDistributionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDistributionETagInitialConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDistributionExists(resourceName, &distribution), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.#", "1"), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.0.access_control_allow_headers.#", "1"), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.0.access_control_allow_headers.0.items.#", "1"), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.0.access_control_allow_headers.0.items.0", "test"), + resource.TestCheckResourceAttr(resourceName, "comment", "Some comment"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "retain_on_delete", + "wait_for_deployment", + }, + }, + { + Config: testAccDistributionETagUpdatedConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDistributionExists(resourceName, &distribution), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.#", "1"), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.0.access_control_allow_headers.#", "1"), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.0.access_control_allow_headers.0.items.#", "2"), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.0.access_control_allow_headers.0.items.0", "test"), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.0.access_control_allow_headers.0.items.1", "updated"), + resource.TestCheckResourceAttr(resourceName, "comment", "Some comment"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "retain_on_delete", + "wait_for_deployment", + }, + }, + { + Config: testAccDistributionETagFinalConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDistributionExists(resourceName, &distribution), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.#", "1"), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.0.access_control_allow_headers.#", "1"), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.0.access_control_allow_headers.0.items.#", "2"), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.0.access_control_allow_headers.0.items.0", "test"), + resource.TestCheckResourceAttr("aws_cloudfront_response_headers_policy.example", "cors_config.0.access_control_allow_headers.0.items.1", "updated"), + resource.TestCheckResourceAttr(resourceName, "comment", "Updated comment"), + ), + }, + }, + }) +} + func testAccCheckDistributionDestroy(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).CloudFrontConn @@ -3847,3 +3919,405 @@ resource "aws_cloudfront_distribution" "test" { } `, item)) } + +func testAccDistributionETagInitialConfig(rName string) string { + return acctest.ConfigCompose( + logBucket(rName), + fmt.Sprintf(` +resource "aws_cloudfront_cache_policy" "example" { + name = "test-policy-%[1]s" + comment = "test comment" + default_ttl = 50 + max_ttl = 100 + min_ttl = 1 + parameters_in_cache_key_and_forwarded_to_origin { + cookies_config { + cookie_behavior = "whitelist" + cookies { + items = ["test"] + } + } + headers_config { + header_behavior = "whitelist" + headers { + items = ["test"] + } + } + query_strings_config { + query_string_behavior = "whitelist" + query_strings { + items = ["test"] + } + } + } +} + +resource "aws_cloudfront_response_headers_policy" "example" { + name = "test-policy-%[1]s" + comment = "test comment" + + cors_config { + access_control_allow_credentials = true + + access_control_allow_headers { + items = ["test"] + } + + access_control_allow_methods { + items = ["GET"] + } + + access_control_allow_origins { + items = ["test.example.comtest"] + } + + origin_override = true + } +} + +resource "aws_cloudfront_origin_request_policy" "test_policy" { + name = "test-policy-%[1]s" + comment = "test comment" + cookies_config { + cookie_behavior = "whitelist" + cookies { + items = ["test"] + } + } + headers_config { + header_behavior = "whitelist" + headers { + items = ["test"] + } + } + query_strings_config { + query_string_behavior = "whitelist" + query_strings { + items = ["test"] + } + } +} + +resource "aws_cloudfront_distribution" "main" { + origin { + domain_name = "www.example.com" + origin_id = "myCustomOrigin" + + custom_origin_config { + http_port = 80 + https_port = 443 + origin_protocol_policy = "http-only" + origin_ssl_protocols = ["SSLv3", "TLSv1"] + origin_read_timeout = 30 + origin_keepalive_timeout = 5 + } + } + + enabled = true + comment = "Some comment" + default_root_object = "index.html" + + logging_config { + include_cookies = false + bucket = aws_s3_bucket.s3_bucket_logs.bucket_regional_domain_name + prefix = "myprefix" + } + + default_cache_behavior { + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD"] + target_origin_id = "myCustomOrigin" + smooth_streaming = false + + origin_request_policy_id = aws_cloudfront_origin_request_policy.test_policy.id + cache_policy_id = aws_cloudfront_cache_policy.example.id + response_headers_policy_id = aws_cloudfront_response_headers_policy.example.id + + viewer_protocol_policy = "allow-all" + } + + price_class = "PriceClass_200" + + restrictions { + geo_restriction { + restriction_type = "whitelist" + locations = ["US", "CA", "GB", "DE"] + } + } + + viewer_certificate { + cloudfront_default_certificate = true + } + + %[2]s +} +`, rName, testAccDistributionRetainConfig())) +} + +func testAccDistributionETagUpdatedConfig(rName string) string { + return acctest.ConfigCompose( + logBucket(rName), + fmt.Sprintf(` +resource "aws_cloudfront_cache_policy" "example" { + name = "test-policy-%[1]s" + comment = "test comment" + default_ttl = 50 + max_ttl = 100 + min_ttl = 1 + parameters_in_cache_key_and_forwarded_to_origin { + cookies_config { + cookie_behavior = "whitelist" + cookies { + items = ["test"] + } + } + headers_config { + header_behavior = "whitelist" + headers { + items = ["test"] + } + } + query_strings_config { + query_string_behavior = "whitelist" + query_strings { + items = ["test"] + } + } + } +} + +resource "aws_cloudfront_response_headers_policy" "example" { + name = "test-policy-%[1]s" + comment = "test comment" + + cors_config { + access_control_allow_credentials = true + + access_control_allow_headers { + items = ["test", "updated"] + } + + access_control_allow_methods { + items = ["GET"] + } + + access_control_allow_origins { + items = ["test.example.comtest"] + } + + origin_override = true + } +} + +resource "aws_cloudfront_origin_request_policy" "test_policy" { + name = "test-policy-%[1]s" + comment = "test comment" + cookies_config { + cookie_behavior = "whitelist" + cookies { + items = ["test"] + } + } + headers_config { + header_behavior = "whitelist" + headers { + items = ["test"] + } + } + query_strings_config { + query_string_behavior = "whitelist" + query_strings { + items = ["test"] + } + } +} + +resource "aws_cloudfront_distribution" "main" { + origin { + domain_name = "www.example.com" + origin_id = "myCustomOrigin" + + custom_origin_config { + http_port = 80 + https_port = 443 + origin_protocol_policy = "http-only" + origin_ssl_protocols = ["SSLv3", "TLSv1"] + origin_read_timeout = 30 + origin_keepalive_timeout = 5 + } + } + + enabled = true + comment = "Some comment" + default_root_object = "index.html" + + logging_config { + include_cookies = false + bucket = aws_s3_bucket.s3_bucket_logs.bucket_regional_domain_name + prefix = "myprefix" + } + + default_cache_behavior { + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD"] + target_origin_id = "myCustomOrigin" + smooth_streaming = false + + origin_request_policy_id = aws_cloudfront_origin_request_policy.test_policy.id + cache_policy_id = aws_cloudfront_cache_policy.example.id + response_headers_policy_id = aws_cloudfront_response_headers_policy.example.id + + viewer_protocol_policy = "allow-all" + } + + price_class = "PriceClass_200" + + restrictions { + geo_restriction { + restriction_type = "whitelist" + locations = ["US", "CA", "GB", "DE"] + } + } + + viewer_certificate { + cloudfront_default_certificate = true + } + + %[2]s +} +`, rName, testAccDistributionRetainConfig())) +} + +func testAccDistributionETagFinalConfig(rName string) string { + return acctest.ConfigCompose( + logBucket(rName), + fmt.Sprintf(` +resource "aws_cloudfront_cache_policy" "example" { + name = "test-policy-%[1]s" + comment = "test comment" + default_ttl = 50 + max_ttl = 100 + min_ttl = 1 + parameters_in_cache_key_and_forwarded_to_origin { + cookies_config { + cookie_behavior = "whitelist" + cookies { + items = ["test"] + } + } + headers_config { + header_behavior = "whitelist" + headers { + items = ["test"] + } + } + query_strings_config { + query_string_behavior = "whitelist" + query_strings { + items = ["test"] + } + } + } +} + +resource "aws_cloudfront_response_headers_policy" "example" { + name = "test-policy-%[1]s" + comment = "test comment" + + cors_config { + access_control_allow_credentials = true + + access_control_allow_headers { + items = ["test", "updated"] + } + + access_control_allow_methods { + items = ["GET"] + } + + access_control_allow_origins { + items = ["test.example.comtest"] + } + + origin_override = true + } +} + +resource "aws_cloudfront_origin_request_policy" "test_policy" { + name = "test-policy-%[1]s" + comment = "test comment" + cookies_config { + cookie_behavior = "whitelist" + cookies { + items = ["test"] + } + } + headers_config { + header_behavior = "whitelist" + headers { + items = ["test"] + } + } + query_strings_config { + query_string_behavior = "whitelist" + query_strings { + items = ["test"] + } + } +} + +resource "aws_cloudfront_distribution" "main" { + origin { + domain_name = "www.example.com" + origin_id = "myCustomOrigin" + + custom_origin_config { + http_port = 80 + https_port = 443 + origin_protocol_policy = "http-only" + origin_ssl_protocols = ["SSLv3", "TLSv1"] + origin_read_timeout = 30 + origin_keepalive_timeout = 5 + } + } + + enabled = true + comment = "Updated comment" + default_root_object = "index.html" + + logging_config { + include_cookies = false + bucket = aws_s3_bucket.s3_bucket_logs.bucket_regional_domain_name + prefix = "myprefix" + } + + default_cache_behavior { + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + cached_methods = ["GET", "HEAD"] + target_origin_id = "myCustomOrigin" + smooth_streaming = false + + origin_request_policy_id = aws_cloudfront_origin_request_policy.test_policy.id + cache_policy_id = aws_cloudfront_cache_policy.example.id + response_headers_policy_id = aws_cloudfront_response_headers_policy.example.id + + viewer_protocol_policy = "allow-all" + } + + price_class = "PriceClass_200" + + restrictions { + geo_restriction { + restriction_type = "whitelist" + locations = ["US", "CA", "GB", "DE"] + } + } + + viewer_certificate { + cloudfront_default_certificate = true + } + + %[2]s +} +`, rName, testAccDistributionRetainConfig())) +} From 41b231deabc7a1ab2bce53cede21d97572f77a85 Mon Sep 17 00:00:00 2001 From: Andrew Titmuss Date: Wed, 4 May 2022 23:27:17 +1000 Subject: [PATCH 2/2] update CHANGELOG --- .changelog/24537.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/24537.txt diff --git a/.changelog/24537.txt b/.changelog/24537.txt new file mode 100644 index 00000000000..d45f7aed4da --- /dev/null +++ b/.changelog/24537.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_cloudfront_distribution: Fix PreconditionFailed errors when other CloudFront resources are changed before the distribution +```