|
1 | 1 | package {{ package_name }};
|
2 | 2 |
|
3 |
| -import java.util.function.BiFunction; |
4 |
| -import java.util.function.Function; |
5 |
| -import lombok.var; |
6 |
| -import software.amazon.awssdk.core.SdkClient; |
| 3 | +import java.util.Objects; |
| 4 | +import software.amazon.awssdk.awscore.AwsRequest; |
| 5 | +import software.amazon.awssdk.awscore.AwsResponse; |
| 6 | +import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException; |
| 7 | +import software.amazon.cloudformation.exceptions.CfnInvalidRequestException; |
| 8 | +import software.amazon.cloudformation.exceptions.CfnNotFoundException; |
| 9 | +import software.amazon.cloudformation.exceptions.CfnResourceConflictException; |
7 | 10 | import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
|
8 | 11 | import software.amazon.cloudformation.proxy.Logger;
|
9 | 12 | import software.amazon.cloudformation.proxy.ProgressEvent;
|
10 | 13 | import software.amazon.cloudformation.proxy.ProxyClient;
|
11 | 14 | import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
|
12 | 15 |
|
13 | 16 | public class {{ operation }}Handler extends BaseHandlerStd {
|
| 17 | + private static final String CREATED_RESOURCE_STATUS = "available"; |
| 18 | + private Logger logger; |
| 19 | + |
14 | 20 | protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
|
15 |
| - final AmazonWebServicesClientProxy proxy, |
16 |
| - final ResourceHandlerRequest<ResourceModel> request, |
17 |
| - final CallbackContext callbackContext, |
18 |
| - final ProxyClient<SdkClient> proxyClient, |
19 |
| - final Logger logger) { |
| 21 | + final AmazonWebServicesClientProxy proxy, |
| 22 | + final ResourceHandlerRequest<ResourceModel> request, |
| 23 | + final CallbackContext callbackContext, |
| 24 | + final ProxyClient<ServiceSdkClient> proxyClient, |
| 25 | + final Logger logger) { |
| 26 | + |
| 27 | + this.logger = logger; |
20 | 28 |
|
21 | 29 | final ResourceModel model = request.getDesiredResourceState();
|
22 | 30 |
|
23 | 31 | // TODO: Adjust Progress Chain according to your implementation
|
24 | 32 |
|
25 |
| - return proxy.initiate("Service-Name::{{ operation }}-Custom-Resource", proxyClient, model, callbackContext) |
26 |
| - .request(createResourceRequest) // construct a body of a request |
27 |
| - .call(executeCreateRequest) // make an api call |
28 |
| - .stabilize(commonStabilizer) // stabilize is describing the resource until it is in a certain status |
29 |
| - .progress() |
30 |
| - .then(postCreationUpdate); // post stabilization update |
| 33 | + return ProgressEvent.progress(model, callbackContext) |
| 34 | + .then(progress -> checkForPreCreateResourceExistence(proxy, request, progress)) |
| 35 | + .then(progress -> |
| 36 | + // If your service API throws 'ResourceAlreadyExistsException' for create requests then CreateHandler can return just proxy.initiate construction |
| 37 | + proxy.initiate("Service-Name::{{ operation }}-Custom-Resource", proxyClient, model, callbackContext) |
| 38 | + .request(Translator::translateToCreateRequest) // construct a body of a request |
| 39 | + .call(this::createResource) // make an api call |
| 40 | + .stabilize(this::stabilizeOnCreate) // stabilize is describing the resource until it is in a certain status |
| 41 | + .progress() |
| 42 | + .then(progress -> postCreateUpdate(progress, proxyClient)); // post stabilization update |
| 43 | + ) |
| 44 | + .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)); |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * If your service API does not distinguish duplicate create requests against some identifier (e.g; resource Name) |
| 49 | + * and instead returns a 200 even though a resource already exists, you must first check if the resource exists here |
| 50 | + * NOTE: If your service API throws 'ResourceAlreadyExistsException' for create requests this method is not necessary |
| 51 | + * @param model |
| 52 | + */ |
| 53 | + ProgressEvent<ResourceModel, CallbackContext> checkForPreCreateResourceExistence( |
| 54 | + final AmazonWebServicesClientProxy proxy, |
| 55 | + final ResourceHandlerRequest<ResourceModel> request, |
| 56 | + final ProgressEvent<ResourceModel, CallbackContext> progressEvent) { |
| 57 | + final ResourceModel model = progressEvent.getResourceModel(); |
| 58 | + final CallbackContext callbackContext = progressEvent.getCallbackContext(); |
| 59 | + try { |
| 60 | + new ReadHandler().handleRequest(proxy, request, callbackContext, logger); |
| 61 | + throw new CfnAlreadyExistsException(ResourceModel.TYPE_NAME, Objects.toString(model.getPrimaryIdentifier())); |
| 62 | + } catch (CfnNotFoundException e) { |
| 63 | + logger.log(model.getPrimaryIdentifier() + " does not exist; creating the resource."); |
| 64 | + return ProgressEvent.progress(model, callbackContext); |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * Implement client invocation of the create request through the proxyClient, which is already initialised with |
| 70 | + * caller credentials, correct region and retry settings |
| 71 | + * @param awsRequest |
| 72 | + * @param proxyClient |
| 73 | + * @return |
| 74 | + */ |
| 75 | + AwsResponse createResource( |
| 76 | + final AwsRequest awsRequest, |
| 77 | + final ProxyClient<ServiceSdkClient> proxyClient) { |
| 78 | + AwsResponse awsResponse; |
| 79 | + try { |
| 80 | + awsResponse = proxyClient.injectCredentialsAndInvokeV2(awsRequest, ClientBuilder.getClient()::executeCreateRequest); |
| 81 | + } catch (final CfnResourceConflictException e) { |
| 82 | + throw new CfnInvalidRequestException(ResourceModel.TYPE_NAME, Objects.toString(model.getPrimaryIdentifier())); |
| 83 | + } |
| 84 | + |
| 85 | + final String message = String.format("%s [%s] successfully created.", ResourceModel.TYPE_NAME, model.getPrimaryIdentifier()); |
| 86 | + logger.log(message); |
| 87 | + |
| 88 | + return awsResponse; |
31 | 89 | }
|
32 | 90 |
|
33 |
| - // Sample lambda function to construct a create request |
34 |
| - final Function<ResourceModel, Object> createResourceRequest = Translator::sampleCreateResourceRequest; |
35 |
| - |
36 |
| - // Inputs: {AwsRequest, SampleSdkClient} | Output: {AwsResponse} |
37 |
| - final BiFunction<Object, ProxyClient<SdkClient>, Object> executeCreateRequest = |
38 |
| - ( |
39 |
| - final Object awsRequest, // AwsRequest |
40 |
| - final ProxyClient<SdkClient> proxyClient |
41 |
| - ) -> { |
42 |
| - // TODO: Implement client invocation of the request |
43 |
| - // hint: should return proxyClient.injectCredentialsAndInvokeV2(awsRequest, SampleSdkClient::execute); |
44 |
| - final var awsResponse = awsRequest; |
45 |
| - return awsResponse; // AwsResponse |
46 |
| - }; |
47 |
| - |
48 |
| - // Sample lambda function to do subsequent operations after resource has been created/stabilized |
49 |
| - final Function<ProgressEvent<ResourceModel, CallbackContext>, ProgressEvent<ResourceModel, CallbackContext>> postCreationUpdate = this::modifyResourceOtherProperties; |
50 |
| - |
51 |
| - // put additional logic that is {{ operation }}Handler specific |
| 91 | + /** |
| 92 | + * To account for eventual consistency, ensure you stabilize your create request so that a |
| 93 | + * subsequent Read will successfully describe the resource state that was provisioned |
| 94 | + */ |
| 95 | + boolean stabilizeOnCreate( |
| 96 | + final ResourceModel model, |
| 97 | + final ProxyClient<ServiceSdkClient> proxyClient) { |
| 98 | + final AwsRequest readRequest = Translator.translateToReadRequest(model); |
| 99 | + final AwsResponse readResponse = proxyClient.injectCredentialsAndInvokeV2(readRequest, ClientBuilder.getClient()::executeReadRequest); |
| 100 | + |
| 101 | + final status = readRequest.getResourceStatus(); |
| 102 | + |
| 103 | + final boolean stabilized = status.equals(CREATED_RESOURCE_STATUS); |
| 104 | + |
| 105 | + if (stabalized) { |
| 106 | + logger.log(String.format("%s [%s] is in %s state. Stabilized.", ResourceModel.TYPE_NAME, model.getPrimaryIdentifier(), CREATED_RESOURCE_STATUS)); |
| 107 | + } else { |
| 108 | + logger.log(String.format("%s [%s] is in %s state. Stabilization is in progress.", ResourceModel.TYPE_NAME, model.getPrimaryIdentifier(), status)); |
| 109 | + } |
| 110 | + return stabilized; |
| 111 | + } |
| 112 | + |
| 113 | + /** |
| 114 | + * If your resource is provisioned through multiple API calls, you will need to apply each post-create update |
| 115 | + * step in a discrete call/stabilize chain to ensure the entire resource is provisioned as intended. |
| 116 | + * @param progressEvent |
| 117 | + * @return |
| 118 | + */ |
| 119 | + ProgressEvent<ResourceModel, CallbackContext> postCreateUpdate( |
| 120 | + final ProgressEvent<ResourceModel, CallbackContext> progressEvent, |
| 121 | + final ProxyClient<ServiceSdkClient> proxyClient) { |
| 122 | + try { |
| 123 | + proxyClient.injectCredentialsAndInvokeV2(Translator.translateToUpdateRequest(model), ClientBuilder.getClient()::executeUpdateResource); |
| 124 | + } catch (final CfnResourceConflictException e) { |
| 125 | + throw new CfnInvalidRequestException(ResourceModel.TYPE_NAME, Objects.toString(model.getPrimaryIdentifier())); |
| 126 | + } |
| 127 | + |
| 128 | + final String message = String.format("%s [%s] successfully updated.", ResourceModel.TYPE_NAME, model.getPrimaryIdentifier()); |
| 129 | + logger.log(message); |
| 130 | + } |
52 | 131 | }
|
0 commit comments