From 82e137c59a59e3bac6c68fd828e1b2e430a4f65e Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Wed, 15 Mar 2023 17:44:53 +0100 Subject: [PATCH 01/13] initial docs changes --- docs/utilities/custom_resources.md | 37 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index 1f2acf36e..de6b60f9e 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -3,17 +3,14 @@ title: Custom Resources description: Utility --- -[Custom resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) +[CloudFormation Custom resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) provide a way for [AWS Lambda functions]( https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html) to execute -provisioning logic whenever CloudFormation stacks are created, updated, or deleted. The CloudFormation utility enables -developers to write these Lambda functions in Java. +provisioning logic whenever CloudFormation stacks are created, updated, or deleted. -The utility provides a base `AbstractCustomResourceHandler` class which handles [custom resource request events]( -https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html), constructs -[custom resource responses](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html), and -sends them to the custom resources. Subclasses implement the provisioning logic and configure certain properties of -these response objects. +Powertools-cloudformation makes it easy to write Lambda functions in Java that are used as CloudFormation custom resources. +The utility reads incoming CloudFormation events, calls your custom code depending on the operation (CREATE, UPDATE or DELETE) and sends responses back to CloudFormation. +By using this library you do not need to write code to integrate with CloudFormation, and you only focus on writing the custom provisioning logic inside the Lambda function. ## Install @@ -40,11 +37,14 @@ To install this utility, add the following dependency to your project. ## Usage -Create a new `AbstractCustomResourceHandler` subclass and implement the `create`, `update`, and `delete` methods with -provisioning logic in the appropriate methods(s). +To utilise the feature, extend the `AbstractCustomResourceHandler` class in your Lambda handler class. +After you extend the `AbstractCustomResourceHandler`, implement and override the following 3 methods: `create`, `update` and `delete`. The `AbstractCustomResourceHandler` invokes the right method according to the CloudFormation [custom resource request event]( +https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html) it receives. +Inside the methods, implement your custom provisioning logic, and return a `Response`. The `AbstractCustomResourceHandler` takes your `Response`, builds a +[custom resource responses](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html) and sends it to CloudFormation automatically. -As an example, if a Lambda function only needs to provision something when a stack is created, put the provisioning -logic exclusively within the `create` method; the other methods can just return `null`. +Custom resources notify cloudformation either of `SUCCESS` or `FAILED` status. You have 2 utility methods to represent these responses: `Response.success(physicalResourceId)` and `Response.failed(physicalResourceId)`. +If a `Response` is not returned by your code, `AbstractCustomResourceHandler` defaults the response to `SUCCESS`. ```java hl_lines="8 9 10 11" import com.amazonaws.services.lambda.runtime.Context; @@ -56,8 +56,13 @@ public class ProvisionOnCreateHandler extends AbstractCustomResourceHandler { @Override protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { - doProvisioning(); - return Response.success(); + String physicalResourceId = "sample-resource-id-" + UUID.randomUUID(); //Create a unique ID for your resource + ProvisioningResult provisioningResult = doProvisioning(); + if(provisioningResult.isSuccessful()){ //check if the provisioning was successful + return Response.success(physicalResourceId); + }else{ + return Response.failed(physicalResourceId); + } } @Override @@ -74,8 +79,8 @@ public class ProvisionOnCreateHandler extends AbstractCustomResourceHandler { ### Signaling Provisioning Failures -If provisioning fails, the stack creation/modification/deletion as a whole can be failed by either throwing a -`RuntimeException` or by explicitly returning a `Response` with a failed status, e.g. `Response.failure()`. +If the provisioning inside your Custom Resource fails, you can notify CloudFormation of the failure by returning a `Repsonse.failure(physicalResourceId)`. + ### Configuring Response Objects From d697967dab8fbec38ff3f1b0d91936e4a0c5c728 Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Thu, 16 Mar 2023 12:12:00 +0100 Subject: [PATCH 02/13] more doc changes --- docs/utilities/custom_resources.md | 56 ++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index de6b60f9e..3db092fb2 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -43,8 +43,11 @@ https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests Inside the methods, implement your custom provisioning logic, and return a `Response`. The `AbstractCustomResourceHandler` takes your `Response`, builds a [custom resource responses](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html) and sends it to CloudFormation automatically. -Custom resources notify cloudformation either of `SUCCESS` or `FAILED` status. You have 2 utility methods to represent these responses: `Response.success(physicalResourceId)` and `Response.failed(physicalResourceId)`. -If a `Response` is not returned by your code, `AbstractCustomResourceHandler` defaults the response to `SUCCESS`. +Custom resources notify cloudformation either of `SUCCESS` or `FAILED` status. You have 2 utility methods to represent these responses: `Response.success(physicalResourceId)` and `Response.failed(physicalResourceId)`. +The `physicalResourceId` is an identifier that is used during the lifecycle operations of the Custom Resource. +You should supply a `physicalResourceId` during the `CREATE` operation, CloudFormation stores the `physicalResourceId` and includes it `UPDATE` and `DELETE` events. + +Here an example of how to implement a Custom Resource using the powertools-cloudformation library: ```java hl_lines="8 9 10 11" import com.amazonaws.services.lambda.runtime.Context; @@ -52,12 +55,12 @@ import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResource import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler; import software.amazon.lambda.powertools.cloudformation.Response; -public class ProvisionOnCreateHandler extends AbstractCustomResourceHandler { +public class MyCustomResourceHandler extends AbstractCustomResourceHandler { @Override protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { String physicalResourceId = "sample-resource-id-" + UUID.randomUUID(); //Create a unique ID for your resource - ProvisioningResult provisioningResult = doProvisioning(); + ProvisioningResult provisioningResult = doProvisioning(physicalResourceId); if(provisioningResult.isSuccessful()){ //check if the provisioning was successful return Response.success(physicalResourceId); }else{ @@ -67,35 +70,57 @@ public class ProvisionOnCreateHandler extends AbstractCustomResourceHandler { @Override protected Response update(CloudFormationCustomResourceEvent updateEvent, Context context) { - return null; + String physicalResourceId = updateEvent.getPhysicalResourceId(); //Get the PhysicalResourceId from CloudFormation + UpdateResult updateResult = doUpdates(physicalResourceId); + if(updateResult.isSuccessful()){ //check if the update operations were successful + return Response.success(physicalResourceId); + }else{ + return Response.failed(physicalResourceId); + } } @Override protected Response delete(CloudFormationCustomResourceEvent deleteEvent, Context context) { - return null; + String physicalResourceId = deleteEvent.getPhysicalResourceId(); //Get the PhysicalResourceId from CloudFormation + DeleteResult deleteResult = doDeletes(physicalResourceId); + if(deleteResult.isSuccessful()){ //check if the delete operations were successful + return Response.success(physicalResourceId); + }else{ + return Response.failed(physicalResourceId); + } } } ``` -### Signaling Provisioning Failures +### Missing `Response` and exception handling + +If a `Response` is not returned by your code, `AbstractCustomResourceHandler` defaults the response to `SUCCESS`. +If your code raises an exception (which is not handled), the `AbstractCustomResourceHandler` defaults the response to `FAILED`. + +In both of the scenarios, powertools-java will assign the `physicalResourceId` based on the following logic: +- if present, use the `physicalResourceId` provided in the `CloudFormationCustomResourceEvent` +- if it is not present, use the `LogStreamName` from the Lambda context -If the provisioning inside your Custom Resource fails, you can notify CloudFormation of the failure by returning a `Repsonse.failure(physicalResourceId)`. +#### Why does this matter? +It is recommended that you always provide a `physicalResourceId` in your response because `physicalResourceId` has a crucial role in the lifecycle of a CloudFormation custom resource. +If the `physicalResourceId` changes between calls from Cloudformation, for instance in response to an `Update` event, Cloudformation [treats the resource update as a replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html). -### Configuring Response Objects +### Customising a response -When provisioning results in data to be shared with other parts of the stack, include this data within the returned -`Response` instance. +As well as the `Response.success(physicalResourceId)` and `Response.failed(physicalResourceId)`, you can customise the `Response` by using the `Response.builder()`. +You customise the responses when you need additional attributes to be shared with other parts of the CloudFormation stack. -This Lambda function creates a [Chime AppInstance](https://docs.aws.amazon.com/chime/latest/dg/create-app-instance.html) +In the example below, the Lambda function creates a [Chime AppInstance](https://docs.aws.amazon.com/chime/latest/dg/create-app-instance.html) and maps the returned ARN to a "ChimeAppInstanceArn" attribute. ```java hl_lines="11 12 13 14" public class ChimeAppInstanceHandler extends AbstractCustomResourceHandler { @Override protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { + String physicalResourceId = "my-app-name-" + UUID.randomUUID(); //Create a unique ID CreateAppInstanceRequest chimeRequest = CreateAppInstanceRequest.builder() - .name("my-app-name") + .name(physicalResourceId) .build(); CreateAppInstanceResponse chimeResponse = ChimeClient.builder() .region("us-east-1") @@ -104,6 +129,8 @@ public class ChimeAppInstanceHandler extends AbstractCustomResourceHandler { Map chimeAtts = Map.of("ChimeAppInstanceArn", chimeResponse.appInstanceArn()); return Response.builder() .value(chimeAtts) + .status(Response.Status.SUCCESS) + .physicalResourceId(physicalResourceId) .build(); } } @@ -118,6 +145,7 @@ For the example above the following response payload will be sent. "StackId": "arn:aws:cloudformation:us-east-1:123456789000:stack/Custom-stack/59e4d2d0-2fe2-10ec-b00e-124d7c1c5f15", "RequestId": "7cae0346-0359-4dff-b80a-a82f247467b6", "LogicalResourceId:": "ChimeTriggerResource", + "PhysicalResourceId:": "my-app-name-db4a47b9-0cac-45ba-8cc4-a480490c5779", "NoEcho": false, "Data": { "ChimeAppInstanceArn": "arn:aws:chime:us-east-1:123456789000:app-instance/150972c2-5490-49a9-8ba7-e7da4257c16a" @@ -125,7 +153,7 @@ For the example above the following response payload will be sent. } ``` -Once the custom resource receives this response, it's "ChimeAppInstanceArn" attribute is set and the +Once the custom resource receives this response, its "ChimeAppInstanceArn" attribute is set and the [Fn::GetAtt function]( https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html) may be used to retrieve the attribute value and make it available to other resources in the stack. From 0373c1c1f7b418db9749ddab8f9eec56294c0133 Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Thu, 16 Mar 2023 13:09:42 +0100 Subject: [PATCH 03/13] more doc changes --- docs/utilities/custom_resources.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index 3db092fb2..f0142cf4a 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -45,7 +45,7 @@ Inside the methods, implement your custom provisioning logic, and return a `Resp Custom resources notify cloudformation either of `SUCCESS` or `FAILED` status. You have 2 utility methods to represent these responses: `Response.success(physicalResourceId)` and `Response.failed(physicalResourceId)`. The `physicalResourceId` is an identifier that is used during the lifecycle operations of the Custom Resource. -You should supply a `physicalResourceId` during the `CREATE` operation, CloudFormation stores the `physicalResourceId` and includes it `UPDATE` and `DELETE` events. +You should generate a `physicalResourceId` during the `CREATE` operation, CloudFormation stores the `physicalResourceId` and includes it `UPDATE` and `DELETE` events. Here an example of how to implement a Custom Resource using the powertools-cloudformation library: @@ -167,7 +167,10 @@ with the Fn::GetAtt function. public class SensitiveDataHandler extends AbstractResourceHandler { @Override protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { + String physicalResourceId = "my-sensitive-resource-" + UUID.randomUUID(); //Create a unique ID return Response.builder() + .status(Response.Status.SUCCESS) + .physicalResourceId(physicalResourceId) .value(Map.of("SomeSecret", sensitiveValue)) .noEcho(true) .build(); @@ -201,8 +204,11 @@ public class CustomSerializationHandler extends AbstractResourceHandler { @Override protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { + String physicalResourceId = "my-policy-name-" + UUID.randomUUID(); //Create a unique ID Policy policy = new Policy(); return Response.builder() + .status(Response.Status.SUCCESS) + .physicalResourceId(physicalResourceId) .value(policy) .objectMapper(policyMapper) // customize serialization .build(); From 6c2ae4cfe6117333d23b4349bfab2b74cf0c7837 Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Thu, 16 Mar 2023 13:36:02 +0100 Subject: [PATCH 04/13] more doc changes --- docs/utilities/custom_resources.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index f0142cf4a..28d5f8e18 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -49,7 +49,7 @@ You should generate a `physicalResourceId` during the `CREATE` operation, CloudF Here an example of how to implement a Custom Resource using the powertools-cloudformation library: -```java hl_lines="8 9 10 11" +```java hl_lines="10-16 21-27 32-38" import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler; @@ -101,7 +101,7 @@ In both of the scenarios, powertools-java will assign the `physicalResourceId` b - if present, use the `physicalResourceId` provided in the `CloudFormationCustomResourceEvent` - if it is not present, use the `LogStreamName` from the Lambda context -#### Why does this matter? +#### Why do you need a physicalResourceId? It is recommended that you always provide a `physicalResourceId` in your response because `physicalResourceId` has a crucial role in the lifecycle of a CloudFormation custom resource. If the `physicalResourceId` changes between calls from Cloudformation, for instance in response to an `Update` event, Cloudformation [treats the resource update as a replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html). @@ -114,7 +114,7 @@ You customise the responses when you need additional attributes to be shared wit In the example below, the Lambda function creates a [Chime AppInstance](https://docs.aws.amazon.com/chime/latest/dg/create-app-instance.html) and maps the returned ARN to a "ChimeAppInstanceArn" attribute. -```java hl_lines="11 12 13 14" +```java hl_lines="12-17" public class ChimeAppInstanceHandler extends AbstractCustomResourceHandler { @Override protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { @@ -163,7 +163,7 @@ retrieve the attribute value and make it available to other resources in the sta If any attributes are sensitive, enable the "noEcho" flag to mask the output of the custom resource when it's retrieved with the Fn::GetAtt function. -```java hl_lines="6" +```java hl_lines="9" public class SensitiveDataHandler extends AbstractResourceHandler { @Override protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { @@ -184,7 +184,7 @@ Although using a `Map` as the Response's value is the most straightforward way t any arbitrary `java.lang.Object` may be used. By default, these objects are serialized with an internal Jackson `ObjectMapper`. If the object requires special serialization logic, a custom `ObjectMapper` can be specified. -```java hl_lines="21 22 23 24" +```java hl_lines="14-16 26" public class CustomSerializationHandler extends AbstractResourceHandler { /** * Type representing the custom response Data. From 7cea7818f203df670024d33769361b0570ecb29f Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Thu, 16 Mar 2023 18:00:06 +0100 Subject: [PATCH 05/13] more doc changes --- docs/utilities/custom_resources.md | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index 28d5f8e18..222fced76 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -215,3 +215,78 @@ public class CustomSerializationHandler extends AbstractResourceHandler { } } ``` + +## Advanced + +### Understanding the CloudFormation custom resource lifecycle + +While the library provides an easy-to-use interface, we recommend that you understand the lifecycle of CloudFormation custom resources before using them in production. + +#### Creating a custom resource +When CloudFormation issues a CREATE on a custom resource, there are 2 possible states: `CREATE_COMPLETE` and `CREATE_FAILED` +```mermaid +stateDiagram + direction LR + createState: Create custom resource + [*] --> createState + createState --> CREATE_COMPLETE + createState --> CREATE_FAILED +``` + +If the resource is created successfully, the `physicalResourceId` is stored by CloudFormation for future operations. +If the resource failed to create, CloudFormation triggers a rollback operation by default (rollback can be disabled, see [stack failure options](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stack-failure-options.html)) + +#### Updating a custom resource +CloudFormation issues an UPDATE operation on a custom resource only when one or more custom resource properties change. +During the update, the custom resource may update successfully, or may fail the update. +```mermaid +stateDiagram + direction LR + updateState: Update custom resource + [*] --> updateState + updateState --> UPDATE_COMPLETE + updateState --> UPDATE_FAILED +``` + +In both of these scenarios, the custom resource can return the same `physicalResourceId` it received in the CLoudFormation event, or a different `physicalResourceId`. +Semantically an `UPDATE_COMPLETE` that returns the same `physicalResourceId` it received, means that the existing resource was updated successfully. +Instead, an `UPDATE_COMPLETE` with a different `physicalResourceId` means that a new physical resource was created successfully. +```mermaid +flowchart BT + id1(Logical resource) + id2(Previous physical Resource) + id3(New physical Resource) + id2 --> id1 + id3 --> id1 +``` +Therefore, after the custom resource update completed or failed, there may be other cleanup operations by Cloudformation during the rollback, as described in the diagram below: +```mermaid +stateDiagram + state if_state <> + updateState: Update custom resource + deletePrev: DELETE resource with previous physicalResourceId + updatePrev: Rollback - UPDATE resource with previous properties + noOp: No further operations + [*] --> updateState + updateState --> UPDATE_COMPLETE + UPDATE_COMPLETE --> if_state + if_state --> noOp : Same physicalResourceId + if_state --> deletePrev : Different physicalResourceId + updateState --> UPDATE_FAILED + UPDATE_FAILED --> updatePrev +``` + +#### Deleting a custom resource + +CloudFormation issues a DELETE on a custom resource when: +- the CloudFormation stack is being deleted +- a new `physicalResourceId` was received during an update (see previous section) + +```mermaid +stateDiagram + direction LR + deleteState: Delete custom resource + [*] --> deleteState + deleteState --> DELETE_COMPLETE + deleteState --> DELETE_FAILED +``` \ No newline at end of file From e609e07324db63d029b205e89f32cfddc9c4e430 Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Thu, 16 Mar 2023 18:05:23 +0100 Subject: [PATCH 06/13] more doc changes --- docs/utilities/custom_resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index 222fced76..e25ddcf1b 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -248,7 +248,7 @@ stateDiagram updateState --> UPDATE_FAILED ``` -In both of these scenarios, the custom resource can return the same `physicalResourceId` it received in the CLoudFormation event, or a different `physicalResourceId`. +In both of these scenarios, the custom resource can return the same `physicalResourceId` it received in the CloudFormation event, or a different `physicalResourceId`. Semantically an `UPDATE_COMPLETE` that returns the same `physicalResourceId` it received, means that the existing resource was updated successfully. Instead, an `UPDATE_COMPLETE` with a different `physicalResourceId` means that a new physical resource was created successfully. ```mermaid From b30a8fa452a458f61669f53086a16a0d81eb5005 Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Mon, 20 Mar 2023 11:15:53 +0100 Subject: [PATCH 07/13] Update docs/utilities/custom_resources.md improvements from PR Co-authored-by: Scott Gerring --- docs/utilities/custom_resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index e25ddcf1b..24a5dfcd6 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -38,7 +38,7 @@ To install this utility, add the following dependency to your project. ## Usage To utilise the feature, extend the `AbstractCustomResourceHandler` class in your Lambda handler class. -After you extend the `AbstractCustomResourceHandler`, implement and override the following 3 methods: `create`, `update` and `delete`. The `AbstractCustomResourceHandler` invokes the right method according to the CloudFormation [custom resource request event]( +Next, implement and override the following 3 methods: `create`, `update` and `delete`. The `AbstractCustomResourceHandler` invokes the right method according to the CloudFormation [custom resource request event]( https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html) it receives. Inside the methods, implement your custom provisioning logic, and return a `Response`. The `AbstractCustomResourceHandler` takes your `Response`, builds a [custom resource responses](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html) and sends it to CloudFormation automatically. From fba5e571bc9359d6da8af0d21f338bf6fb0c2ea1 Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Mon, 20 Mar 2023 11:16:04 +0100 Subject: [PATCH 08/13] Update docs/utilities/custom_resources.md improvements from PR Co-authored-by: Scott Gerring --- docs/utilities/custom_resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index 24a5dfcd6..cba87a27b 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -45,7 +45,7 @@ Inside the methods, implement your custom provisioning logic, and return a `Resp Custom resources notify cloudformation either of `SUCCESS` or `FAILED` status. You have 2 utility methods to represent these responses: `Response.success(physicalResourceId)` and `Response.failed(physicalResourceId)`. The `physicalResourceId` is an identifier that is used during the lifecycle operations of the Custom Resource. -You should generate a `physicalResourceId` during the `CREATE` operation, CloudFormation stores the `physicalResourceId` and includes it `UPDATE` and `DELETE` events. +You should generate a `physicalResourceId` during the `CREATE` operation, CloudFormation stores the `physicalResourceId` and includes it in `UPDATE` and `DELETE` events. Here an example of how to implement a Custom Resource using the powertools-cloudformation library: From b5c4bff7bd004fa06ae7427e5ec75c3e8f0cfa82 Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Mon, 20 Mar 2023 11:16:13 +0100 Subject: [PATCH 09/13] Update docs/utilities/custom_resources.md improvements from PR Co-authored-by: Scott Gerring --- docs/utilities/custom_resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index cba87a27b..e85b2f78e 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -97,7 +97,7 @@ public class MyCustomResourceHandler extends AbstractCustomResourceHandler { If a `Response` is not returned by your code, `AbstractCustomResourceHandler` defaults the response to `SUCCESS`. If your code raises an exception (which is not handled), the `AbstractCustomResourceHandler` defaults the response to `FAILED`. -In both of the scenarios, powertools-java will assign the `physicalResourceId` based on the following logic: +In both of the scenarios, powertools-java will return the `physicalResourceId` to CloudFormation based on the following logic: - if present, use the `physicalResourceId` provided in the `CloudFormationCustomResourceEvent` - if it is not present, use the `LogStreamName` from the Lambda context From 2d7d5a3f9de0fc6dfddcd4bbdab8e9a682776d6c Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Mon, 20 Mar 2023 11:17:28 +0100 Subject: [PATCH 10/13] Update docs/utilities/custom_resources.md improvements from PR Co-authored-by: Scott Gerring --- docs/utilities/custom_resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index e85b2f78e..5ccf7a93f 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -103,7 +103,7 @@ In both of the scenarios, powertools-java will return the `physicalResourceId` t #### Why do you need a physicalResourceId? -It is recommended that you always provide a `physicalResourceId` in your response because `physicalResourceId` has a crucial role in the lifecycle of a CloudFormation custom resource. +It is recommended that you always explicitly provide a `physicalResourceId` in your response rather than letting powertools generate if for you because `physicalResourceId` has a crucial role in the lifecycle of a CloudFormation custom resource. If the `physicalResourceId` changes between calls from Cloudformation, for instance in response to an `Update` event, Cloudformation [treats the resource update as a replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html). ### Customising a response From c5bb489151604c4a400d51cac428517dc94621e0 Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Mon, 20 Mar 2023 11:18:53 +0100 Subject: [PATCH 11/13] Update docs/utilities/custom_resources.md PR Feedback Co-authored-by: Scott Gerring --- docs/utilities/custom_resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index 5ccf7a93f..598f6d88b 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -249,7 +249,7 @@ stateDiagram ``` In both of these scenarios, the custom resource can return the same `physicalResourceId` it received in the CloudFormation event, or a different `physicalResourceId`. -Semantically an `UPDATE_COMPLETE` that returns the same `physicalResourceId` it received, means that the existing resource was updated successfully. +Semantically an `UPDATE_COMPLETE` that returns the same `physicalResourceId` it received indicates that the existing resource was updated successfully. Instead, an `UPDATE_COMPLETE` with a different `physicalResourceId` means that a new physical resource was created successfully. ```mermaid flowchart BT From 1300bfeb02d4be519b485a136756cef0d24b0cd7 Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Mon, 20 Mar 2023 11:33:24 +0100 Subject: [PATCH 12/13] Feedback from PR --- docs/utilities/custom_resources.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index 598f6d88b..3857f2975 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -98,8 +98,8 @@ If a `Response` is not returned by your code, `AbstractCustomResourceHandler` de If your code raises an exception (which is not handled), the `AbstractCustomResourceHandler` defaults the response to `FAILED`. In both of the scenarios, powertools-java will return the `physicalResourceId` to CloudFormation based on the following logic: -- if present, use the `physicalResourceId` provided in the `CloudFormationCustomResourceEvent` -- if it is not present, use the `LogStreamName` from the Lambda context +- For CREATE operations, the `LogStreamName` from the Lambda context is used. +- For UPDATE and DELETE operations, the `physicalResourceId` provided in the `CloudFormationCustomResourceEvent` is used. #### Why do you need a physicalResourceId? From b5cb50b9e5d6ae8ace8b499fce7322a7d74a7f1f Mon Sep 17 00:00:00 2001 From: Michele Ricciardi Date: Mon, 20 Mar 2023 11:34:42 +0100 Subject: [PATCH 13/13] adding more context --- docs/utilities/custom_resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/custom_resources.md b/docs/utilities/custom_resources.md index 3857f2975..d9f8494c3 100644 --- a/docs/utilities/custom_resources.md +++ b/docs/utilities/custom_resources.md @@ -280,7 +280,7 @@ stateDiagram CloudFormation issues a DELETE on a custom resource when: - the CloudFormation stack is being deleted -- a new `physicalResourceId` was received during an update (see previous section) +- a new `physicalResourceId` was received during an update, and CloudFormation proceeds to rollback(DELETE) the custom resource with the previous `physicalResourceId`. ```mermaid stateDiagram