Skip to content

Commit 81ce911

Browse files
author
Joe Wolf
committed
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 aws-powertools#558
1 parent b087302 commit 81ce911

File tree

7 files changed

+757
-68
lines changed

7 files changed

+757
-68
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package software.amazon.lambda.powertools.cloudformation;
2+
3+
import com.amazonaws.services.lambda.runtime.Context;
4+
import com.amazonaws.services.lambda.runtime.RequestHandler;
5+
import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import software.amazon.awssdk.http.SdkHttpClient;
9+
import software.amazon.lambda.powertools.cloudformation.CloudFormationResponse.ResponseStatus;
10+
11+
import java.io.IOException;
12+
import java.util.Objects;
13+
14+
/**
15+
* Handler base class providing core functionality for sending responses to custom CloudFormation resources after
16+
* receiving some event. Depending on the type of event, this class either invokes the crete, update, or delete method
17+
* and sends the returned Response object to the custom resource.
18+
*/
19+
public abstract class AbstractCustomResourceHandler
20+
implements RequestHandler<CloudFormationCustomResourceEvent, Response> {
21+
22+
private static final Logger LOG = LoggerFactory.getLogger(AbstractCustomResourceHandler.class);
23+
24+
private final SdkHttpClient client;
25+
26+
/**
27+
* Creates a new Handler that uses the provided HTTP client for communicating with custom CloudFormation resources.
28+
*
29+
* @param client cannot be null
30+
*/
31+
public AbstractCustomResourceHandler(SdkHttpClient client) {
32+
this.client = Objects.requireNonNull(client, "SdkHttpClient cannot be null.");
33+
}
34+
35+
/**
36+
* Generates the appropriate response object based on the event type and sends it as a response to the custom
37+
* cloud formation resource using the URL provided within the event.
38+
*
39+
* @param event custom resources create/update/delete event
40+
* @param context lambda execution context
41+
* @return potentially null response object sent to the custom resource
42+
*/
43+
@Override
44+
public final Response handleRequest(CloudFormationCustomResourceEvent event, Context context) {
45+
String responseUrl = Objects.requireNonNull(event.getResponseUrl(),
46+
"Event must have a non-null responseUrl to be able to send the response.");
47+
48+
CloudFormationResponse client = buildResponseClient();
49+
50+
Response response = null;
51+
try {
52+
response = getResponse(event, context);
53+
LOG.debug("Preparing to send response {} to {}.", response, responseUrl);
54+
client.send(event, context, ResponseStatus.SUCCESS, response);
55+
} catch (IOException ioe) {
56+
LOG.error("Unable to send {} success to {}.", responseUrl, ioe);
57+
onSendFailure(event, context, response, ioe);
58+
} catch (ResponseException rse) {
59+
LOG.error("Unable to create/serialize Response. Sending empty failure to {}", responseUrl, rse);
60+
// send a failure with a null response on account of response serialization issues
61+
try {
62+
client.send(event, context, ResponseStatus.FAILED);
63+
} catch (Exception e) {
64+
// unable to serialize response AND send an empty response
65+
LOG.error("Unable to send failure to {}.", responseUrl, e);
66+
onSendFailure(event, context, null, e);
67+
}
68+
}
69+
return response;
70+
}
71+
72+
private Response getResponse(CloudFormationCustomResourceEvent event, Context context)
73+
throws ResponseException {
74+
try {
75+
switch (event.getRequestType()) {
76+
case "Create":
77+
return create(event, context);
78+
case "Update":
79+
return update(event, context);
80+
case "Delete":
81+
return delete(event, context);
82+
default:
83+
LOG.warn("Unexpected request type \"" + event.getRequestType() + "\" for event " + event);
84+
return null;
85+
}
86+
} catch (RuntimeException e) {
87+
throw new ResponseException("Unable to get Response", e);
88+
}
89+
}
90+
91+
/**
92+
* Builds a client for sending responses to the custom resource.
93+
*
94+
* @return a client for sending the response
95+
*/
96+
protected CloudFormationResponse buildResponseClient() {
97+
return new CloudFormationResponse(client);
98+
}
99+
100+
/**
101+
* Invoked when there is an error sending a response to the custom cloud formation resource. This method does not
102+
* get called if there are errors constructing the response itself, which instead is handled by sending an empty
103+
* FAILED response to the custom resource. This method will be invoked, however, if there is an error while sending
104+
* the FAILED response.
105+
* <p>
106+
* The method itself does nothing but subclasses may override to provide additional logging or handling logic. All
107+
* arguments provided are for contextual purposes.
108+
* <p>
109+
* Exceptions should not be thrown by this method.
110+
*
111+
* @param event the event
112+
* @param context execution context
113+
* @param response the response object that was attempted to be sent to the custom resource
114+
* @param exception the exception caught when attempting to call the custom resource URL
115+
*/
116+
@SuppressWarnings("unused")
117+
protected void onSendFailure(CloudFormationCustomResourceEvent event,
118+
Context context,
119+
Response response,
120+
Exception exception) {
121+
// intentionally empty
122+
}
123+
124+
/**
125+
* Returns the response object to send to the custom CloudFormation resource upon its creation. If this method
126+
* returns null, then the handler will send a successful but empty response to the CloudFormation resource. If this
127+
* method throws a RuntimeException, the handler will send an empty failed response to the resource.
128+
*
129+
* @param event an event of request type Create
130+
* @param context execution context
131+
* @return the response object or null
132+
*/
133+
protected abstract Response create(CloudFormationCustomResourceEvent event, Context context);
134+
135+
/**
136+
* Returns the response object to send to the custom CloudFormation resource upon its modification. If the method
137+
* returns null, then the handler will send a successful but empty response to the CloudFormation resource. If this
138+
* method throws a RuntimeException, the handler will send an empty failed response to the resource.
139+
*
140+
* @param event an event of request type Update
141+
* @param context execution context
142+
* @return the response object or null
143+
*/
144+
protected abstract Response update(CloudFormationCustomResourceEvent event, Context context);
145+
146+
/**
147+
* Returns the response object to send to the custom CloudFormation resource upon its deletion. If this method
148+
* returns null, then the handler will send a successful but empty response to the CloudFormation resource. If this
149+
* method throws a RuntimeException, the handler will send an empty failed response to the resource.
150+
*
151+
* @param event an event of request type Delete
152+
* @param context execution context
153+
* @return the response object or null
154+
*/
155+
protected abstract Response delete(CloudFormationCustomResourceEvent event, Context context);
156+
}

0 commit comments

Comments
 (0)