Skip to content

Commit bcdc050

Browse files
committed
added more definitive examples to handler stubs
1 parent a7a884c commit bcdc050

File tree

10 files changed

+374
-231
lines changed

10 files changed

+374
-231
lines changed

python/rpdk/java/codegen.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,8 @@
1717

1818
OPERATIONS = ("Create", "Read", "Update", "Delete", "List")
1919
EXECUTABLE = "cfn"
20-
AWSCODEGEN = namedtuple(
21-
"AWSCODEGEN",
22-
"default guided default_code guided_code",
23-
defaults=("default", "guided_aws", "1", "2"),
24-
)
25-
CODEGEN = AWSCODEGEN()
20+
AWSCODEGEN = namedtuple("AWSCODEGEN", "default guided default_code guided_code")
21+
CODEGEN = AWSCODEGEN("default", "guided_aws", "1", "2")
2622

2723

2824
def logdebug(func: object):

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,24 @@ public class AbstractTestBase {
1919
logger = new LoggerProxy();
2020
}
2121
static ProxyClient<SdkClient> MOCK_PROXY(
22-
final AmazonWebServicesClientProxy proxy,
23-
final SdkClient sdkClient
24-
) {
22+
final AmazonWebServicesClientProxy proxy,
23+
final ServiceSdkClient sdkClient) {
2524
return new ProxyClient<SdkClient>() {
2625
@Override
27-
public <RequestT extends AwsRequest, ResponseT extends AwsResponse>
28-
ResponseT
26+
public <RequestT extends AwsRequest, ResponseT extends AwsResponse> ResponseT
2927
injectCredentialsAndInvokeV2(RequestT request, Function<RequestT, ResponseT> requestFunction) {
3028
return proxy.injectCredentialsAndInvokeV2(request, requestFunction);
3129
}
3230

3331
@Override
3432
public <RequestT extends AwsRequest, ResponseT extends AwsResponse>
3533
CompletableFuture<ResponseT>
36-
injectCredentialsAndInvokeV2Aync(RequestT request,
37-
Function<RequestT, CompletableFuture<ResponseT>> requestFunction) {
34+
injectCredentialsAndInvokeV2Aync(RequestT request, Function<RequestT, CompletableFuture<ResponseT>> requestFunction) {
3835
throw new UnsupportedOperationException();
3936
}
4037

4138
@Override
42-
public SdkClient client() {
39+
public ServiceSdkClient client() {
4340
return sdkClient;
4441
}
4542
};
Lines changed: 16 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package {{ package_name }};
22

3-
import software.amazon.awssdk.core.SdkClient;
43
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
5-
import software.amazon.cloudformation.proxy.CallChain.Callback;
64
import software.amazon.cloudformation.proxy.Logger;
75
import software.amazon.cloudformation.proxy.ProgressEvent;
86
import software.amazon.cloudformation.proxy.ProxyClient;
@@ -11,54 +9,27 @@
119
public abstract class BaseHandlerStd extends BaseHandler<CallbackContext> {
1210
@Override
1311
public final ProgressEvent<ResourceModel, CallbackContext> handleRequest(
14-
final AmazonWebServicesClientProxy proxy,
15-
final ResourceHandlerRequest<ResourceModel> request,
16-
final CallbackContext callbackContext,
17-
final Logger logger) {
12+
final AmazonWebServicesClientProxy proxy,
13+
final ResourceHandlerRequest<ResourceModel> request,
14+
final CallbackContext callbackContext,
15+
final Logger logger) {
1816
return handleRequest(
19-
proxy,
20-
request,
21-
callbackContext != null ? callbackContext : new CallbackContext(),
22-
proxy.newProxy(ClientBuilder::getClient),
23-
logger
17+
proxy,
18+
request,
19+
callbackContext != null ? callbackContext : new CallbackContext(),
20+
proxy.newProxy(ClientBuilder::getClient),
21+
logger
2422
);
2523
}
2624

2725
protected abstract ProgressEvent<ResourceModel, CallbackContext> handleRequest(
28-
final AmazonWebServicesClientProxy proxy,
29-
final ResourceHandlerRequest<ResourceModel> request,
30-
final CallbackContext callbackContext,
31-
final ProxyClient<SdkClient> proxyClient,
32-
final Logger logger);
26+
final AmazonWebServicesClientProxy proxy,
27+
final ResourceHandlerRequest<ResourceModel> request,
28+
final CallbackContext callbackContext,
29+
final ProxyClient<SdkClient> proxyClient,
30+
final Logger logger);
3331

3432
/*
35-
* Methods and Functions that could be shared by Handlers should be in this class
36-
* Some placeholder for useful functions
37-
* */
38-
39-
40-
// waiting for a resource to be in a "ready" state
41-
final Callback<Object, Object, SdkClient, ResourceModel, CallbackContext, Boolean> commonStabilizer =
42-
(
43-
final Object awsRequest, // AwsRequest
44-
final Object awsResponse, // AwsResponse
45-
final ProxyClient<SdkClient> proxy,
46-
final ResourceModel model,
47-
final CallbackContext context
48-
) -> isResourceStabilized();
49-
50-
protected boolean isResourceStabilized() {
51-
// TODO: Stabilization logic - describing the resource until the in a "ready" state
52-
return true;
53-
}
54-
55-
protected ProgressEvent<ResourceModel, CallbackContext> modifyResourceProperty(final ProgressEvent<ResourceModel, CallbackContext> progressEvent) {
56-
// TODO: Update logic - return progress event
57-
return ProgressEvent.progress(progressEvent.getResourceModel(), progressEvent.getCallbackContext());
58-
}
59-
60-
protected ProgressEvent<ResourceModel, CallbackContext> modifyResourceOtherProperties(final ProgressEvent<ResourceModel, CallbackContext> progressEvent) {
61-
// TODO: Alternative Update logic if couldn't be implemented inside modifyResourceProperty - return progress event
62-
return ProgressEvent.success(progressEvent.getResourceModel(), progressEvent.getCallbackContext());
63-
}
33+
* Common or abstract methods and functions should be in this class
34+
* */
6435
}
Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,8 @@
11
package {{ package_name }};
22

3-
import software.amazon.awssdk.awscore.AwsRequest;
4-
import software.amazon.awssdk.awscore.AwsResponse;
5-
import software.amazon.awssdk.core.SdkClient;
6-
73
public class ClientBuilder {
8-
// sample client builder - change SampleSdkClient on the desired one
9-
public static SampleSdkClient getClient() {
10-
return new SampleSdkClient();
11-
}
12-
13-
// Sample code snippet - remove when implementing handlers
14-
public static class SampleSdkClient implements SdkClient {
15-
public AwsResponse executeRequest(final AwsRequest awsRequest) {
16-
return null;
17-
}
18-
19-
@Override
20-
public String serviceName() {
21-
return null;
22-
}
23-
24-
@Override
25-
public void close() {}
4+
// sample client builder - change ServiceSdkClient on the desired one
5+
public static ServiceSdkClient getClient() {
6+
return ServiceSdkClient.create();
267
}
278
}
Lines changed: 113 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,131 @@
11
package {{ package_name }};
22

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;
710
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
811
import software.amazon.cloudformation.proxy.Logger;
912
import software.amazon.cloudformation.proxy.ProgressEvent;
1013
import software.amazon.cloudformation.proxy.ProxyClient;
1114
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
1215

1316
public class {{ operation }}Handler extends BaseHandlerStd {
17+
private static final String CREATED_RESOURCE_STATUS = "available";
18+
private Logger logger;
19+
1420
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;
2028

2129
final ResourceModel model = request.getDesiredResourceState();
2230

2331
// TODO: Adjust Progress Chain according to your implementation
2432

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;
3189
}
3290

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+
}
52131
}

0 commit comments

Comments
 (0)