Skip to content

Commit 043afc5

Browse files
author
Joe Wolf
committed
Cloudformation module documentation
1 parent 4b9e7c0 commit 043afc5

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed

docs/utilities/custom_resources.md

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
---
2+
title: Custom Resources description: Utility
3+
---
4+
5+
[Custom resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html)
6+
provide a way for [AWS lambdas](
7+
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources-lambda.html) to execute
8+
provisioning logic whenever CloudFormation stacks are created, updated, or deleted. The CloudFormation utility enables
9+
developers to write these lambdas in Java.
10+
11+
The utility provides a base `AbstractCustomResourceHandler` class that listens for [custom resource request events](
12+
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html), constructs
13+
[custom resource responses](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html), and
14+
sends them to the custom resources. Subclasses implement the provisioning logic and configure certain properties of
15+
these response objects.
16+
17+
## Install
18+
19+
To install this utility, add the following dependency to your project.
20+
21+
=== "Maven"
22+
23+
```xml
24+
<dependency>
25+
<groupId>software.amazon.lambda</groupId>
26+
<artifactId>powertools-cloudformation</artifactId>
27+
<version>1.7.3</version>
28+
</dependency>
29+
```
30+
31+
=== "Gradle"
32+
33+
```groovy
34+
dependencies {
35+
...
36+
implementation 'software.amazon.lambda:powertools-cloudformation:1.7.3'
37+
aspectpath 'software.amazon.lambda:powertools-cloudformation:1.7.3'
38+
}
39+
```
40+
41+
## Usage
42+
43+
Create a new `AbstractCustomResourceHandler` subclass and implement the `create`, `update`, and `delete` methods with
44+
provisioning logic in the appropriate methods(s).
45+
46+
As an example, if a lambda only needs to provision something when a stack is created, put the provisioning logic
47+
exclusively within the `create` method; the other methods can just return `null`.
48+
49+
```java hl_lines="8 9 10 11"
50+
import com.amazonaws.services.lambda.runtime.Context;
51+
import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
52+
import software.amazon.lambda.powertools.cloudformation.AbstractCustomResourceHandler;
53+
import software.amazon.lambda.powertools.cloudformation.Response;
54+
55+
public class ProvisionOnCreateHandler extends AbstractCustomResourceHandler {
56+
57+
@Override
58+
protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) {
59+
doProvisioning();
60+
return Response.success();
61+
}
62+
63+
@Override
64+
protected Response update(CloudFormationCustomResourceEvent updateEvent, Context context) {
65+
return null;
66+
}
67+
68+
@Override
69+
protected Response delete(CloudFormationCustomResourceEvent deleteEvent, Context context) {
70+
return null;
71+
}
72+
}
73+
```
74+
75+
### Signaling Provisioning Failures
76+
77+
If provisioning fails, the stack creation/modification/deletion as a whole can be failed by either throwing a
78+
`RuntimeException` or by explicitly returning a `Response` with a failed status, e.g. `Response.failure()`.
79+
80+
### Configuring Response Objects
81+
82+
When provisioning results in data to be shared with other parts of the stack, include this data within the returned
83+
`Response` instance.
84+
85+
This lambda creates a [Chime AppInstance](https://docs.aws.amazon.com/chime/latest/dg/create-app-instance.html) and maps
86+
the returned ARN to a "ChimeAppInstanceArn" attribute.
87+
88+
```java hl_lines="11 12 13 14"
89+
public class ChimeAppInstanceHandler extends AbstractCustomResourceHandler {
90+
@Override
91+
protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) {
92+
CreateAppInstanceRequest chimeRequest = CreateAppInstanceRequest.builder()
93+
.name("my-app-name")
94+
.build();
95+
CreateAppInstanceResponse chimeResponse = ChimeClient.builder()
96+
.region("us-east-1")
97+
.createAppInstance(chimeRequest);
98+
99+
Map<String, String> chimeAtts = Map.of("ChimeAppInstanceArn", chimeResponse.appInstanceArn());
100+
return Response.builder()
101+
.value(chimeAtts)
102+
.build();
103+
}
104+
}
105+
```
106+
107+
Assuming the method executes successfully, the handler will send a response to the custom resource with a payload that
108+
looks something like the following:
109+
110+
```json
111+
{
112+
"Status": "SUCCESS",
113+
"PhysicalResourceId": "2021/10/01/e3a37e552eff4718a5675c1e31f0649e",
114+
"StackId": "arn:aws:cloudformation:us-east-1:123456789000:stack/Custom-stack/59e4d2d0-2fe2-10ec-b00e-124d7c1c5f15",
115+
"RequestId": "7cae0346-0359-4dff-b80a-a82f247467b6",
116+
"LogicalResourceId:": "ChimeTriggerResource",
117+
"NoEcho": false,
118+
"Data": {
119+
"ChimeAppInstanceArn": "arn:aws:chime:us-east-1:123456789000:app-instance/150972c2-5490-49a9-8ba7-e7da4257c16a"
120+
}
121+
}
122+
```
123+
124+
Once the custom resource receives this response, it's "ChimeAppInstanceArn" attribute is set and the
125+
[Fn::GetAtt function](
126+
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html) may be used to
127+
retrieve the attribute value and make it available to other resources in the stack.
128+
129+
#### Sensitive Response Data
130+
131+
If any attributes are sensitive, enable the "noEcho" flag to mask the output of the custom resource when it's retrieved
132+
with the Fn::GetAtt function.
133+
134+
```java hl_lines="6"
135+
public class SensitiveDataHandler extends AbstractResourceHandler {
136+
@Override
137+
protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) {
138+
return Response.builder()
139+
.value(Map.of("SomeSecret", sensitiveValue))
140+
.noEcho(true)
141+
.build();
142+
}
143+
}
144+
```
145+
146+
#### Customizing Serialization
147+
148+
Although using a `Map` as the Response's value is the most straightforward way to provide attribute name/value pairs,
149+
any arbitrary `java.lang.Object` may be used. By default, these objects are serialized with an internal Jackson
150+
`ObjectMapper`. If the object requires special serialization logic, a custom `ObjectMapper` can be specified.
151+
152+
```java hl_lines="21 22 23 24"
153+
public class CustomSerializationHandler extends AbstractResourceHandler {
154+
/**
155+
* Type representing the custom response Data.
156+
*/
157+
static class Policy {
158+
public ZonedDateTime getExpires() {
159+
return ZonedDateTime.now().plusDays(10);
160+
}
161+
}
162+
163+
/**
164+
* Mapper for serializing Policy instances.
165+
*/
166+
private final ObjectMapper policyMapper = new ObjectMapper()
167+
.registerModule(new JavaTimeModule())
168+
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
169+
170+
@Override
171+
protected Response create(CloudFormationCustomResourceEvent createEvent, Context context) {
172+
Policy policy = new Policy();
173+
return Response.builder()
174+
.value(policy)
175+
.objectMapper(policyMapper) // customize serialization
176+
.build();
177+
}
178+
}
179+
```

0 commit comments

Comments
 (0)