diff --git a/.changelog/19697.txt b/.changelog/19697.txt new file mode 100644 index 00000000000..9adb227ec80 --- /dev/null +++ b/.changelog/19697.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_cloudfront_function: Add `live_etag_version` attribute +``` + +```release-note:bug +resource/aws_cloudfront_function: The `etag` attribute always the `DEVELOPMENT` version's value +``` \ No newline at end of file diff --git a/internal/service/cloudfront/find.go b/internal/service/cloudfront/find.go index 886f5908995..88a2c930630 100644 --- a/internal/service/cloudfront/find.go +++ b/internal/service/cloudfront/find.go @@ -27,7 +27,7 @@ func FindFunctionByNameAndStage(conn *cloudfront.CloudFront, name, stage string) return nil, err } - if output == nil { + if output == nil || output.FunctionSummary == nil { return nil, tfresource.NewEmptyResultError(input) } diff --git a/internal/service/cloudfront/function.go b/internal/service/cloudfront/function.go index 622d84cd36a..2ea9037106b 100644 --- a/internal/service/cloudfront/function.go +++ b/internal/service/cloudfront/function.go @@ -28,40 +28,37 @@ func ResourceFunction() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "code": { Type: schema.TypeString, Required: true, }, - "comment": { Type: schema.TypeString, Optional: true, }, - "etag": { Type: schema.TypeString, Computed: true, }, - + "live_stage_etag": { + Type: schema.TypeString, + Computed: true, + }, "name": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "publish": { Type: schema.TypeBool, Optional: true, Default: true, }, - "runtime": { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringInSlice(cloudfront.FunctionRuntime_Values(), false), }, - "status": { Type: schema.TypeString, Computed: true, @@ -70,8 +67,6 @@ func ResourceFunction() *schema.Resource { } } -// resourceAwsCloudFrontFunction maps to: -// CreateFunction in the API / SDK func resourceFunctionCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).CloudFrontConn @@ -85,7 +80,7 @@ func resourceFunctionCreate(d *schema.ResourceData, meta interface{}) error { Name: aws.String(functionName), } - log.Printf("[DEBUG] Creating CloudFront Function %s", functionName) + log.Printf("[DEBUG] Creating CloudFront Function: %s", functionName) output, err := conn.CreateFunction(input) if err != nil { @@ -100,7 +95,7 @@ func resourceFunctionCreate(d *schema.ResourceData, meta interface{}) error { IfMatch: output.ETag, } - log.Printf("[DEBUG] Publishing Cloudfront Function: %s", input) + log.Printf("[DEBUG] Publishing CloudFront Function: %s", input) _, err := conn.PublishFunction(input) if err != nil { @@ -111,26 +106,19 @@ func resourceFunctionCreate(d *schema.ResourceData, meta interface{}) error { return resourceFunctionRead(d, meta) } -// resourceFunctionRead maps to: -// GetFunction in the API / SDK func resourceFunctionRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).CloudFrontConn - stage := cloudfront.FunctionStageDevelopment - if d.Get("publish").(bool) { - stage = cloudfront.FunctionStageLive - } - - describeFunctionOutput, err := FindFunctionByNameAndStage(conn, d.Id(), stage) + describeFunctionOutput, err := FindFunctionByNameAndStage(conn, d.Id(), cloudfront.FunctionStageDevelopment) if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] CloudFront Function (%s/%s) not found, removing from state", d.Id(), stage) + log.Printf("[WARN] CloudFront Function (%s) not found, removing from state", d.Id()) d.SetId("") return nil } if err != nil { - return fmt.Errorf("error describing CloudFront Function (%s/%s): %w", d.Id(), stage, err) + return fmt.Errorf("error reading CloudFront Function (%s) DEVELOPMENT stage: %w", d.Id(), err) } d.Set("arn", describeFunctionOutput.FunctionSummary.FunctionMetadata.FunctionARN) @@ -142,20 +130,28 @@ func resourceFunctionRead(d *schema.ResourceData, meta interface{}) error { getFunctionOutput, err := conn.GetFunction(&cloudfront.GetFunctionInput{ Name: aws.String(d.Id()), - Stage: aws.String(stage), + Stage: aws.String(cloudfront.FunctionStageDevelopment), }) if err != nil { - return fmt.Errorf("error getting CloudFront Function (%s/%s): %w", d.Id(), stage, err) + return fmt.Errorf("error reading CloudFront Function (%s) DEVELOPMENT stage code: %w", d.Id(), err) } d.Set("code", string(getFunctionOutput.FunctionCode)) + describeFunctionOutput, err = FindFunctionByNameAndStage(conn, d.Id(), cloudfront.FunctionStageLive) + + if tfresource.NotFound(err) { + d.Set("live_stage_etag", "") + } else if err != nil { + return fmt.Errorf("error reading CloudFront Function (%s) LIVE stage: %w", d.Id(), err) + } else { + d.Set("live_stage_etag", describeFunctionOutput.ETag) + } + return nil } -// resourceFunctionUpdate maps to: -// UpdateFunctionCode in the API / SDK func resourceFunctionUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).CloudFrontConn etag := d.Get("etag").(string) @@ -175,7 +171,7 @@ func resourceFunctionUpdate(d *schema.ResourceData, meta interface{}) error { output, err := conn.UpdateFunction(input) if err != nil { - return fmt.Errorf("error updating CloudFront Function (%s) configuration : %w", d.Id(), err) + return fmt.Errorf("error updating CloudFront Function (%s): %w", d.Id(), err) } etag = aws.StringValue(output.ETag) @@ -187,7 +183,7 @@ func resourceFunctionUpdate(d *schema.ResourceData, meta interface{}) error { IfMatch: aws.String(etag), } - log.Printf("[DEBUG] Publishing Cloudfront Function: %s", d.Id()) + log.Printf("[DEBUG] Publishing CloudFront Function: %s", d.Id()) _, err := conn.PublishFunction(input) if err != nil { @@ -198,12 +194,10 @@ func resourceFunctionUpdate(d *schema.ResourceData, meta interface{}) error { return resourceFunctionRead(d, meta) } -// resourceAwsCloudFrontFunction maps to: -// DeleteFunction in the API / SDK func resourceFunctionDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).CloudFrontConn - log.Printf("[INFO] Deleting Cloudfront Function: %s", d.Id()) + log.Printf("[INFO] Deleting CloudFront Function: %s", d.Id()) _, err := conn.DeleteFunction(&cloudfront.DeleteFunctionInput{ Name: aws.String(d.Id()), IfMatch: aws.String(d.Get("etag").(string)), diff --git a/internal/service/cloudfront/function_test.go b/internal/service/cloudfront/function_test.go index d280a393ff6..cf07227054d 100644 --- a/internal/service/cloudfront/function_test.go +++ b/internal/service/cloudfront/function_test.go @@ -99,6 +99,8 @@ func TestAccCloudFrontFunction_publish(t *testing.T) { Config: testAccPublishConfig(rName, false), Check: resource.ComposeTestCheckFunc( testAccCheckFunctionExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "etag", "ETVPDKIKX0DER"), + resource.TestCheckResourceAttr(resourceName, "live_stage_etag", ""), resource.TestCheckResourceAttr(resourceName, "publish", "false"), resource.TestCheckResourceAttr(resourceName, "status", "UNPUBLISHED"), ), @@ -113,6 +115,8 @@ func TestAccCloudFrontFunction_publish(t *testing.T) { Config: testAccPublishConfig(rName, true), Check: resource.ComposeTestCheckFunc( testAccCheckFunctionExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "etag", "ETVPDKIKX0DER"), + resource.TestCheckResourceAttr(resourceName, "live_stage_etag", "ETVPDKIKX0DER"), resource.TestCheckResourceAttr(resourceName, "publish", "true"), resource.TestCheckResourceAttr(resourceName, "status", "UNASSOCIATED"), ), @@ -201,6 +205,45 @@ func TestAccCloudFrontFunction_Update_code(t *testing.T) { }) } +func TestAccCloudFrontFunction_UpdateCodeAndPublish(t *testing.T) { + var conf cloudfront.DescribeFunctionOutput + resourceName := "aws_cloudfront_function.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + 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: testAccCheckCloudfrontFunctionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPublishConfig(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckFunctionExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "etag", "ETVPDKIKX0DER"), + resource.TestCheckResourceAttr(resourceName, "publish", "false"), + resource.TestCheckResourceAttr(resourceName, "status", "UNPUBLISHED"), + ), + }, + { + Config: testAccCodeUpdateConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckFunctionExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "etag", "E3UN6WX5RRO2AG"), + resource.TestCheckResourceAttr(resourceName, "publish", "true"), + resource.TestCheckResourceAttr(resourceName, "status", "UNASSOCIATED"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"publish"}, + }, + }, + }) +} + func TestAccCloudFrontFunction_Update_comment(t *testing.T) { var conf cloudfront.DescribeFunctionOutput resourceName := "aws_cloudfront_function.test" diff --git a/website/docs/r/cloudfront_function.html.markdown b/website/docs/r/cloudfront_function.html.markdown index ef643469bb6..f9ec4801f23 100644 --- a/website/docs/r/cloudfront_function.html.markdown +++ b/website/docs/r/cloudfront_function.html.markdown @@ -46,7 +46,8 @@ The following arguments are optional: In addition to all arguments above, the following attributes are exported: * `arn` - Amazon Resource Name (ARN) identifying your CloudFront Function. -* `etag` - ETag hash of the function +* `etag` - ETag hash of the function. This is the value for the `DEVELOPMENT` stage of the function. +* `live_stage_etag` - ETag hash of any `LIVE` stage of the function. * `status` - Status of the function. Can be `UNPUBLISHED`, `UNASSOCIATED` or `ASSOCIATED`. ## Import