Skip to content

Commit 57ee197

Browse files
authored
fixed create/delete handler pre execution check (#266)
1 parent efdacd7 commit 57ee197

File tree

4 files changed

+202
-360
lines changed

4 files changed

+202
-360
lines changed

python/rpdk/java/templates/init/guided_aws/StubCreateHandler.java

Lines changed: 64 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@
33
// TODO: replace all usage of SdkClient with your service client type, e.g; YourServiceAsyncClient
44
// import software.amazon.awssdk.services.yourservice.YourServiceAsyncClient;
55

6-
import java.util.Objects;
7-
import software.amazon.awssdk.awscore.AwsRequest;
86
import software.amazon.awssdk.awscore.AwsResponse;
97
import software.amazon.awssdk.awscore.exception.AwsServiceException;
108
import software.amazon.awssdk.core.SdkClient;
11-
import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException;
129
import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
13-
import software.amazon.cloudformation.exceptions.CfnNotFoundException;
1410
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
1511
import software.amazon.cloudformation.proxy.Logger;
1612
import software.amazon.cloudformation.proxy.ProgressEvent;
1713
import software.amazon.cloudformation.proxy.ProxyClient;
1814
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
1915

16+
2017
public class CreateHandler extends BaseHandlerStd {
2118
private Logger logger;
2219

@@ -29,157 +26,92 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
2926

3027
this.logger = logger;
3128

32-
final ResourceModel model = request.getDesiredResourceState();
33-
3429
// TODO: Adjust Progress Chain according to your implementation
3530
// https://github.com/aws-cloudformation/cloudformation-cli-java-plugin/blob/master/src/main/java/software/amazon/cloudformation/proxy/CallChain.java
3631

37-
return ProgressEvent.progress(model, callbackContext)
32+
return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
3833

3934
// STEP 1 [check if resource already exists]
4035
// if target API does not support 'ResourceAlreadyExistsException' then following check is required
4136
// for more information -> https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html
42-
//.then(progress -> checkForPreCreateResourceExistence(proxy, request, progress))
37+
.then(progress ->
38+
// STEP 1.0 [initialize a proxy context]
39+
// If your service API is not idempotent, meaning it does not distinguish duplicate create requests against some identifier (e.g; resource Name)
40+
// and instead returns a 200 even though a resource already exists, you must first check if the resource exists here
41+
// NOTE: If your service API throws 'ResourceAlreadyExistsException' for create requests this method is not necessary
42+
proxy.initiate("{{ call_graph }}::{{ operation }}::PreExistanceCheck", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
43+
44+
// STEP 1.1 [TODO: construct a body of a request]
45+
.translateToServiceRequest(Translator::translateToReadRequest)
46+
47+
// STEP 1.2 [TODO: make an api call]
48+
.makeServiceCall((awsRequest, client) -> {
49+
AwsResponse awsResponse = null;
50+
51+
// TODO: add custom read resource logic
52+
53+
logger.log(String.format("%s has successfully been read.", ResourceModel.TYPE_NAME));
54+
return awsResponse;
55+
})
56+
57+
// STEP 1.3 [TODO: handle exception]
58+
.handleError((awsRequest, exception, client, model, context) -> {
59+
// TODO: uncomment when ready to implement
60+
// if (exception instanceof CfnNotFoundException)
61+
// return ProgressEvent.progress(model, context);
62+
// throw exception;
63+
return ProgressEvent.progress(model, context);
64+
})
65+
.progress()
66+
)
4367

4468
// STEP 2 [create/stabilize progress chain - required for resource creation]
4569
.then(progress ->
4670
// If your service API throws 'ResourceAlreadyExistsException' for create requests then CreateHandler can return just proxy.initiate construction
4771
// STEP 2.0 [initialize a proxy context]
48-
proxy.initiate("{{ call_graph }}::{{ operation }}", proxyClient, model, callbackContext)
72+
// Implement client invocation of the create request through the proxyClient, which is already initialised with
73+
// caller credentials, correct region and retry settings
74+
proxy.initiate("{{ call_graph }}::{{ operation }}", proxyClient,progress.getResourceModel(), progress.getCallbackContext())
4975

5076
// STEP 2.1 [TODO: construct a body of a request]
5177
.translateToServiceRequest(Translator::translateToCreateRequest)
5278

5379
// STEP 2.2 [TODO: make an api call]
54-
.makeServiceCall(this::createResource)
80+
.makeServiceCall((awsRequest, client) -> {
81+
AwsResponse awsResponse = null;
82+
try {
83+
84+
// TODO: put your create resource code here
85+
86+
} catch (final AwsServiceException e) {
87+
/*
88+
* While the handler contract states that the handler must always return a progress event,
89+
* you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
90+
* Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
91+
* to more specific error codes
92+
*/
93+
throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e);
94+
}
95+
96+
logger.log(String.format("%s successfully created.", ResourceModel.TYPE_NAME));
97+
return awsResponse;
98+
})
5599

56100
// STEP 2.3 [TODO: stabilize step is not necessarily required but typically involves describing the resource until it is in a certain status, though it can take many forms]
57101
// for more information -> https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html
58-
.stabilize(this::stabilizedOnCreate)
59-
.progress())
60-
61-
// STEP 3 [TODO: post create/stabilize update]
62-
.then(progress ->
63-
// If your resource is provisioned through multiple API calls, you will need to apply each subsequent update
64-
// STEP 3.0 [initialize a proxy context]
65-
proxy.initiate("{{ call_graph }}::post{{ operation }}", proxyClient, model, callbackContext)
66-
67-
// STEP 3.1 [TODO: construct a body of a request]
68-
.translateToServiceRequest(Translator::translateToSecondUpdateRequest)
69-
70-
// STEP 3.2 [TODO: make an api call]
71-
.makeServiceCall(this::postCreate)
102+
// If your resource requires some form of stabilization (e.g. service does not provide strong consistency), you will need to ensure that your code
103+
// accounts for any potential issues, so that a subsequent read/update requests will not cause any conflicts (e.g. NotFoundException/InvalidRequestException)
104+
.stabilize((awsRequest, awsResponse, client, model, context) -> {
105+
// TODO: put your stabilization code here
106+
107+
final boolean stabilized = true;
108+
logger.log(String.format("%s [%s] has been stabilized.", ResourceModel.TYPE_NAME, model.getPrimaryIdentifier()));
109+
return stabilized;
110+
})
72111
.progress()
73112
)
74113

75-
// STEP 4 [TODO: describe call/chain to return the resource model]
114+
// STEP 3 [TODO: describe call/chain to return the resource model]
76115
.then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger));
77116
}
78-
79-
/**
80-
* If your service API is not idempotent, meaning it does not distinguish duplicate create requests against some identifier (e.g; resource Name)
81-
* and instead returns a 200 even though a resource already exists, you must first check if the resource exists here
82-
* NOTE: If your service API throws 'ResourceAlreadyExistsException' for create requests this method is not necessary
83-
* @param proxy Amazon webservice proxy to inject credentials correctly.
84-
* @param request incoming resource handler request
85-
* @param progressEvent event of the previous state indicating success, in progress with delay callback or failed state
86-
* @return progressEvent indicating success, in progress with delay callback or failed state
87-
*/
88-
private ProgressEvent<ResourceModel, CallbackContext> checkForPreCreateResourceExistence(
89-
final AmazonWebServicesClientProxy proxy,
90-
final ResourceHandlerRequest<ResourceModel> request,
91-
final ProgressEvent<ResourceModel, CallbackContext> progressEvent) {
92-
final ResourceModel model = progressEvent.getResourceModel();
93-
final CallbackContext callbackContext = progressEvent.getCallbackContext();
94-
try {
95-
new ReadHandler().handleRequest(proxy, request, callbackContext, logger);
96-
throw new CfnAlreadyExistsException(ResourceModel.TYPE_NAME, Objects.toString(model.getPrimaryIdentifier()));
97-
} catch (CfnNotFoundException e) {
98-
logger.log(model.getPrimaryIdentifier() + " does not exist; creating the resource.");
99-
return ProgressEvent.progress(model, callbackContext);
100-
}
101-
}
102-
103-
/**
104-
* Implement client invocation of the create request through the proxyClient, which is already initialised with
105-
* caller credentials, correct region and retry settings
106-
* @param awsRequest the aws service request to create a resource
107-
* @param proxyClient the aws service client to make the call
108-
* @return awsResponse create resource response
109-
*/
110-
private AwsResponse createResource(
111-
final AwsRequest awsRequest,
112-
final ProxyClient<SdkClient> proxyClient) {
113-
AwsResponse awsResponse = null;
114-
try {
115-
116-
// TODO: put your create resource code here
117-
118-
} catch (final AwsServiceException e) {
119-
/*
120-
* While the handler contract states that the handler must always return a progress event,
121-
* you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
122-
* Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
123-
* to more specific error codes
124-
*/
125-
throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e);
126-
}
127-
128-
logger.log(String.format("%s successfully created.", ResourceModel.TYPE_NAME));
129-
return awsResponse;
130-
}
131-
132-
/**
133-
* If your resource requires some form of stabilization (e.g. service does not provide strong consistency), you will need to ensure that your code
134-
* accounts for any potential issues, so that a subsequent read/update requests will not cause any conflicts (e.g. NotFoundException/InvalidRequestException)
135-
* for more information -> https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html
136-
* @param awsRequest the aws service request to create a resource
137-
* @param awsResponse the aws service response to create a resource
138-
* @param proxyClient the aws service client to make the call
139-
* @param model resource model
140-
* @param callbackContext callback context
141-
* @return boolean state of stabilized or not
142-
*/
143-
private boolean stabilizedOnCreate(
144-
final AwsRequest awsRequest,
145-
final AwsResponse awsResponse,
146-
final ProxyClient<SdkClient> proxyClient,
147-
final ResourceModel model,
148-
final CallbackContext callbackContext) {
149-
150-
// TODO: put your stabilization code here
151-
152-
final boolean stabilized = true;
153-
logger.log(String.format("%s [%s] creation has stabilized: %s", ResourceModel.TYPE_NAME, model.getPrimaryIdentifier(), stabilized));
154-
return stabilized;
155-
}
156-
157-
/**
158-
* If your resource is provisioned through multiple API calls, you will need to apply each subsequent update
159-
* step in a discrete call/stabilize chain to ensure the entire resource is provisioned as intended.
160-
* @param awsRequest the aws service request to create a resource
161-
* @param proxyClient the aws service client to make the call
162-
* @return awsResponse create resource response
163-
*/
164-
private AwsResponse postCreate(
165-
final AwsRequest awsRequest,
166-
final ProxyClient<SdkClient> proxyClient) {
167-
AwsResponse awsResponse = null;
168-
try {
169-
170-
// TODO: put your post creation resource update code here
171-
172-
} catch (final AwsServiceException e) {
173-
/*
174-
* While the handler contract states that the handler must always return a progress event,
175-
* you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
176-
* Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
177-
* to more specific error codes
178-
*/
179-
throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e);
180-
}
181-
182-
logger.log(String.format("%s successfully updated.", ResourceModel.TYPE_NAME));
183-
return awsResponse;
184-
}
185117
}

0 commit comments

Comments
 (0)