-
Notifications
You must be signed in to change notification settings - Fork 89
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Create Java cfn-response equivalent (#558) Introduces Java implementation of cfn-response module based on NodeJS design. Put the sole class in a separate powertools-cloudformation Maven module. 100% code coverage Addresses #558 * Add base RequestHandler class for custom resources Provides abstract methods for generating the Response to be sent, represented as its own type. Use Objects::nonNull instead of custom method. Addresses #558 * Put Response body attributes (status, noEcho, etc) in Response builder itself Instead of method args to various CloudFormationResponse::send methods, reducing the number of polymorphic send methods to two: one with a Response arg and one without. Rename ResponseException to CustomResourceResponseException. AbstractCustomResourceHandlerTest simplifications. * Lock down CloudFormationResponse; default SdkHttpClient - Include powertools-cloudformation in .github config - Address mutatable ObjectMapper spotbugs finding - JavaDoc cleanup * Cloudformation module documentation * CloudFormation documentation tweaks * Include custom resources doc in menu Co-authored-by: Joe Wolf <[email protected]>
- Loading branch information
Showing
13 changed files
with
1,695 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
--- | ||
title: Custom Resources description: Utility | ||
--- | ||
|
||
[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. | ||
|
||
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. | ||
|
||
## Install | ||
|
||
To install this utility, add the following dependency to your project. | ||
|
||
=== "Maven" | ||
|
||
```xml | ||
<dependency> | ||
<groupId>software.amazon.lambda</groupId> | ||
<artifactId>powertools-cloudformation</artifactId> | ||
<version>1.7.3</version> | ||
</dependency> | ||
``` | ||
|
||
=== "Gradle" | ||
|
||
```groovy | ||
dependencies { | ||
... | ||
implementation 'software.amazon.lambda:powertools-cloudformation:1.7.3' | ||
aspectpath 'software.amazon.lambda:powertools-cloudformation:1.7.3' | ||
} | ||
``` | ||
|
||
## Usage | ||
|
||
Create a new `AbstractCustomResourceHandler` subclass and implement the `create`, `update`, and `delete` methods with | ||
provisioning logic in the appropriate methods(s). | ||
|
||
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`. | ||
|
||
```java hl_lines="8 9 10 11" | ||
import com.amazonaws.services.lambda.runtime.Context; | ||
import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; | ||
import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler; | ||
import software.amazon.lambda.powertools.cloudformation.Response; | ||
|
||
public class ProvisionOnCreateHandler extends AbstractCustomResourceHandler { | ||
|
||
@Override | ||
protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { | ||
doProvisioning(); | ||
return Response.success(); | ||
} | ||
|
||
@Override | ||
protected Response update(CloudFormationCustomResourceEvent updateEvent, Context context) { | ||
return null; | ||
} | ||
|
||
@Override | ||
protected Response delete(CloudFormationCustomResourceEvent deleteEvent, Context context) { | ||
return null; | ||
} | ||
} | ||
``` | ||
|
||
### 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()`. | ||
|
||
### Configuring Response Objects | ||
|
||
When provisioning results in data to be shared with other parts of the stack, include this data within the returned | ||
`Response` instance. | ||
|
||
This 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) { | ||
CreateAppInstanceRequest chimeRequest = CreateAppInstanceRequest.builder() | ||
.name("my-app-name") | ||
.build(); | ||
CreateAppInstanceResponse chimeResponse = ChimeClient.builder() | ||
.region("us-east-1") | ||
.createAppInstance(chimeRequest); | ||
|
||
Map<String, String> chimeAtts = Map.of("ChimeAppInstanceArn", chimeResponse.appInstanceArn()); | ||
return Response.builder() | ||
.value(chimeAtts) | ||
.build(); | ||
} | ||
} | ||
``` | ||
|
||
For the example above the following response payload will be sent. | ||
|
||
```json | ||
{ | ||
"Status": "SUCCESS", | ||
"PhysicalResourceId": "2021/10/01/e3a37e552eff4718a5675c1e31f0649e", | ||
"StackId": "arn:aws:cloudformation:us-east-1:123456789000:stack/Custom-stack/59e4d2d0-2fe2-10ec-b00e-124d7c1c5f15", | ||
"RequestId": "7cae0346-0359-4dff-b80a-a82f247467b6", | ||
"LogicalResourceId:": "ChimeTriggerResource", | ||
"NoEcho": false, | ||
"Data": { | ||
"ChimeAppInstanceArn": "arn:aws:chime:us-east-1:123456789000:app-instance/150972c2-5490-49a9-8ba7-e7da4257c16a" | ||
} | ||
} | ||
``` | ||
|
||
Once the custom resource receives this response, it's "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. | ||
|
||
#### Sensitive Response Data | ||
|
||
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" | ||
public class SensitiveDataHandler extends AbstractResourceHandler { | ||
@Override | ||
protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { | ||
return Response.builder() | ||
.value(Map.of("SomeSecret", sensitiveValue)) | ||
.noEcho(true) | ||
.build(); | ||
} | ||
} | ||
``` | ||
|
||
#### Customizing Serialization | ||
|
||
Although using a `Map` as the Response's value is the most straightforward way to provide attribute name/value pairs, | ||
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" | ||
public class CustomSerializationHandler extends AbstractResourceHandler { | ||
/** | ||
* Type representing the custom response Data. | ||
*/ | ||
static class Policy { | ||
public ZonedDateTime getExpires() { | ||
return ZonedDateTime.now().plusDays(10); | ||
} | ||
} | ||
|
||
/** | ||
* Mapper for serializing Policy instances. | ||
*/ | ||
private final ObjectMapper policyMapper = new ObjectMapper() | ||
.registerModule(new JavaTimeModule()) | ||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); | ||
|
||
@Override | ||
protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) { | ||
Policy policy = new Policy(); | ||
return Response.builder() | ||
.value(policy) | ||
.objectMapper(policyMapper) // customize serialization | ||
.build(); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>powertools-cloudformation</artifactId> | ||
<packaging>jar</packaging> | ||
|
||
<parent> | ||
<artifactId>powertools-parent</artifactId> | ||
<groupId>software.amazon.lambda</groupId> | ||
<version>1.7.3</version> | ||
</parent> | ||
|
||
<name>AWS Lambda Powertools Java library Cloudformation</name> | ||
<description> | ||
A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating | ||
custom metrics asynchronously easier. | ||
</description> | ||
<url>https://aws.amazon.com/lambda/</url> | ||
<issueManagement> | ||
<system>GitHub Issues</system> | ||
<url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url> | ||
</issueManagement> | ||
<scm> | ||
<url>https://github.com/awslabs/aws-lambda-powertools-java.git</url> | ||
</scm> | ||
<developers> | ||
<developer> | ||
<name>AWS Lambda Powertools team</name> | ||
<organization>Amazon Web Services</organization> | ||
<organizationUrl>https://aws.amazon.com/</organizationUrl> | ||
</developer> | ||
</developers> | ||
|
||
<distributionManagement> | ||
<snapshotRepository> | ||
<id>ossrh</id> | ||
<url>https://aws.oss.sonatype.org/content/repositories/snapshots</url> | ||
</snapshotRepository> | ||
</distributionManagement> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>software.amazon.awssdk</groupId> | ||
<artifactId>http-client-spi</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>software.amazon.awssdk</groupId> | ||
<artifactId>url-connection-client</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.amazonaws</groupId> | ||
<artifactId>aws-lambda-java-core</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.amazonaws</groupId> | ||
<artifactId>aws-lambda-java-events</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-databind</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.aspectj</groupId> | ||
<artifactId>aspectjrt</artifactId> | ||
</dependency> | ||
|
||
<!-- Test dependencies --> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter-api</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter-engine</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter-params</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mockito</groupId> | ||
<artifactId>mockito-core</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.assertj</groupId> | ||
<artifactId>assertj-core</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
Oops, something went wrong.