Skip to content

Commit cda61c7

Browse files
rajdivyaomkhegderajdivy
authored
Add CloudFormation resource type configuration support (#370)
* Add TypeConfiguration support Co-authored-by: Omkar Hegde <[email protected]> Co-authored-by: Divya Raj <[email protected]>
1 parent 01ee4ab commit cda61c7

30 files changed

+428
-110
lines changed

.github/workflows/pr-ci.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ jobs:
4141
- name: Verify java package
4242
run:
4343
mvn verify
44+
- name: Install java package
45+
run:
46+
mvn install
4447
- name: Integration standard e2e
4548
run:
4649
./e2e_test.sh 1

python/rpdk/java/codegen.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ def generate(self, project):
360360
contents = template.render(
361361
package_name=self.package_name,
362362
operations=project.schema.get("handlers", {}).keys(),
363+
contains_type_configuration=project.configuration_schema,
363364
pojo_name="ResourceModel",
364365
wrapper_parent="LambdaWrapper",
365366
)
@@ -384,12 +385,25 @@ def generate(self, project):
384385
contents = template.render(
385386
package_name=self.package_name,
386387
operations=OPERATIONS,
388+
contains_type_configuration=project.configuration_schema,
387389
pojo_name="ResourceModel",
388390
)
389391
project.overwrite(path, contents)
390392

391393
# generate POJOs
392394
models = resolve_models(project.schema)
395+
if project.configuration_schema:
396+
configuration_schema_path = (
397+
self._get_generated_root(project)
398+
/ project.configuration_schema_filename
399+
)
400+
project.write_configuration_schema(configuration_schema_path)
401+
configuration_models = resolve_models(
402+
project.configuration_schema, "TypeConfigurationModel"
403+
)
404+
else:
405+
configuration_models = {"TypeConfigurationModel": {}}
406+
models.update(configuration_models)
393407

394408
LOG.debug("Writing %d POJOs", len(models))
395409

@@ -416,6 +430,9 @@ def generate(self, project):
416430
package_name=self.package_name,
417431
model_name=model_name,
418432
properties=properties,
433+
no_args_constructor_required=(
434+
model_name != "TypeConfigurationModel" or len(properties) != 0
435+
),
419436
)
420437
project.overwrite(path, contents)
421438

@@ -439,6 +456,7 @@ def _write_executable_wrapper_class(self, src, project):
439456
package_name=self.package_name,
440457
operations=project.schema.get("handlers", {}).keys(),
441458
pojo_name="ResourceModel",
459+
contains_type_configuration=project.configuration_schema,
442460
wrapper_parent="ExecutableWrapper",
443461
)
444462
project.overwrite(path, contents)
@@ -495,7 +513,7 @@ def _find_jar(project):
495513
(project.root / "target").glob("{}-*.jar".format(project.hypenated_name))
496514
)
497515
if not jar_glob:
498-
LOG.debug("No Java ARchives match")
516+
LOG.debug("No Java Archives matched at %s", str(project.root / "target"))
499517
raise JavaArchiveNotFoundError(
500518
"No JAR artifact was found.\n"
501519
"Please run 'mvn package' or the equivalent command "

python/rpdk/java/templates/generate/BaseHandler.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66
import software.amazon.cloudformation.proxy.ProgressEvent;
77
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
88

9-
public abstract class BaseHandler<CallbackT> {
9+
public abstract class BaseHandler<CallbackT{{ ', ConfigurationT' if contains_type_configuration }}> {
1010

1111
public abstract ProgressEvent<{{ pojo_name }}, CallbackT> handleRequest(
1212
final AmazonWebServicesClientProxy proxy,
1313
final ResourceHandlerRequest<{{ pojo_name }}> request,
1414
final CallbackT callbackContext,
15-
final Logger logger);
16-
15+
final Logger logger{{ ',\n final ConfigurationT typeConfiguration' if contains_type_configuration }});
1716
}

python/rpdk/java/templates/generate/HandlerWrapper.java

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,17 @@
4242
import org.json.JSONObject;
4343

4444

45-
public class {{ "HandlerWrapper" if wrapper_parent == "LambdaWrapper" else "HandlerWrapperExecutable" }} extends {{ wrapper_parent }}<{{ pojo_name }}, CallbackContext> {
45+
public class {{ "HandlerWrapper" if wrapper_parent == "LambdaWrapper" else "HandlerWrapperExecutable" }} extends {{ wrapper_parent }}<{{ pojo_name }}, CallbackContext, TypeConfigurationModel> {
4646

4747
private final Configuration configuration = new Configuration();
4848
private JSONObject resourceSchema;
49-
private final Map<Action, BaseHandler<CallbackContext>> handlers = new HashMap<>();
50-
private final static TypeReference<HandlerRequest<{{ pojo_name }}, CallbackContext>> REQUEST_REFERENCE =
51-
new TypeReference<HandlerRequest<{{ pojo_name }}, CallbackContext>>() {};
49+
private final Map<Action, BaseHandler<CallbackContext{{ ', TypeConfigurationModel' if contains_type_configuration }}>> handlers = new HashMap<>();
50+
private final static TypeReference<HandlerRequest<{{ pojo_name }}, CallbackContext, TypeConfigurationModel>> REQUEST_REFERENCE =
51+
new TypeReference<HandlerRequest<{{ pojo_name }}, CallbackContext, TypeConfigurationModel>>() {};
5252
private final static TypeReference<{{ pojo_name }}> TYPE_REFERENCE =
5353
new TypeReference<{{ pojo_name }}>() {};
54-
private final static TypeReference<ResourceHandlerTestPayload<{{ pojo_name }}, CallbackContext>> TEST_ENTRY_TYPE_REFERENCE =
55-
new TypeReference<ResourceHandlerTestPayload<{{ pojo_name }}, CallbackContext>>() {};
54+
private final static TypeReference<ResourceHandlerTestPayload<{{ pojo_name }}, CallbackContext, TypeConfigurationModel>> TEST_ENTRY_TYPE_REFERENCE =
55+
new TypeReference<ResourceHandlerTestPayload<{{ pojo_name }}, CallbackContext, TypeConfigurationModel>>() {};
5656

5757

5858
public {{ "HandlerWrapper" if wrapper_parent == "LambdaWrapper" else "HandlerWrapperExecutable" }}() {
@@ -67,18 +67,22 @@ private void initialiseHandlers() {
6767

6868
@Override
6969
public ProgressEvent<{{ pojo_name }}, CallbackContext> invokeHandler(
70-
final AmazonWebServicesClientProxy proxy,
71-
final ResourceHandlerRequest<{{ pojo_name }}> request,
72-
final Action action,
73-
final CallbackContext callbackContext) {
70+
final AmazonWebServicesClientProxy proxy,
71+
final ResourceHandlerRequest<{{ pojo_name }}> request,
72+
final Action action,
73+
final CallbackContext callbackContext,
74+
final TypeConfigurationModel typeConfiguration) {
75+
7476
final String actionName = (action == null) ? "<null>" : action.toString(); // paranoia
7577
if (!handlers.containsKey(action))
7678
throw new RuntimeException("Unknown action " + actionName);
7779

78-
final BaseHandler<CallbackContext> handler = handlers.get(action);
80+
81+
final BaseHandler<CallbackContext{{ ', TypeConfigurationModel' if contains_type_configuration }}> handler = handlers.get(action);
7982

8083
loggerProxy.log(String.format("[%s] invoking handler...", actionName));
81-
final ProgressEvent<{{ pojo_name }}, CallbackContext> result = handler.handleRequest(proxy, request, callbackContext, loggerProxy);
84+
final ProgressEvent<{{ pojo_name }}, CallbackContext> result = handler.handleRequest(proxy, request,
85+
callbackContext, loggerProxy{{ ', typeConfiguration' if contains_type_configuration }});
8286
loggerProxy.log(String.format("[%s] handler invoked", actionName));
8387
return result;
8488
}
@@ -94,18 +98,20 @@ public void testEntrypoint(
9498
this.loggerProxy = new LoggerProxy();
9599
this.loggerProxy.addLogPublisher(new LambdaLogPublisher(context.getLogger()));
96100

97-
ProgressEvent<{{ pojo_name }}, CallbackContext> response = ProgressEvent.failed(null, null, HandlerErrorCode.InternalFailure, "Uninitialized");
101+
ProgressEvent<{{ pojo_name }}, CallbackContext> response = ProgressEvent.failed(null, null,
102+
HandlerErrorCode.InternalFailure, "Uninitialized");
98103
try {
99104
final String input = IOUtils.toString(inputStream, "UTF-8");
100-
final ResourceHandlerTestPayload<{{ pojo_name }}, CallbackContext> payload =
105+
final ResourceHandlerTestPayload<{{ pojo_name }}, CallbackContext, TypeConfigurationModel> payload =
101106
this.serializer.deserialize(
102107
input,
103108
TEST_ENTRY_TYPE_REFERENCE);
104109

105110
final AmazonWebServicesClientProxy proxy = new AmazonWebServicesClientProxy(
106111
loggerProxy, payload.getCredentials(), () -> (long) context.getRemainingTimeInMillis());
107112

108-
response = invokeHandler(proxy, payload.getRequest(), payload.getAction(), payload.getCallbackContext());
113+
response = invokeHandler(proxy, payload.getRequest(), payload.getAction(), payload.getCallbackContext(),
114+
payload.getTypeConfiguration());
109115
} catch (final BaseHandlerException e) {
110116
response = ProgressEvent.defaultFailureHandler(e, e.getErrorCode());
111117
} catch (final AmazonServiceException | AwsServiceException e) {
@@ -135,7 +141,7 @@ public static void main(String[] args) throws IOException {
135141
System.out.println("__CFN_RESOURCE_END_RESPONSE__");
136142
}
137143

138-
private static void readFileToSystemOut(final String fileName) throws IOException {
144+
private static void readFileToSystemOut(final String fileName) throws IOException {
139145
//Create object of FileReader
140146
final FileReader inputFile = new FileReader(fileName);
141147
try(BufferedReader bufferReader = new BufferedReader(inputFile)) {
@@ -161,8 +167,10 @@ public Map<String, String> provideResourceDefinedTags(final {{ pojo_name}} resou
161167
}
162168

163169
@Override
164-
protected ResourceHandlerRequest<{{ pojo_name }}> transform(final HandlerRequest<{{ pojo_name }}, CallbackContext> request) throws IOException {
165-
final RequestData<{{ pojo_name }}> requestData = request.getRequestData();
170+
protected ResourceHandlerRequest<{{ pojo_name }}> transform(
171+
final HandlerRequest<{{ pojo_name }}, CallbackContext,TypeConfigurationModel> request) throws IOException {
172+
173+
final RequestData<{{ pojo_name }}, TypeConfigurationModel> requestData = request.getRequestData();
166174

167175
return ResourceHandlerRequest.<{{ pojo_name }}>builder()
168176
.clientRequestToken(request.getBearerToken())
@@ -179,7 +187,7 @@ public Map<String, String> provideResourceDefinedTags(final {{ pojo_name}} resou
179187
}
180188

181189
@Override
182-
protected TypeReference<HandlerRequest<{{ pojo_name }}, CallbackContext>> getTypeReference() {
190+
protected TypeReference<HandlerRequest<{{ pojo_name }}, CallbackContext, TypeConfigurationModel>> getTypeReference() {
183191
return REQUEST_REFERENCE;
184192
}
185193

python/rpdk/java/templates/generate/POJO.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,13 @@
88
import java.util.Map;
99
import java.util.List;
1010
import java.util.Set;
11-
import lombok.AllArgsConstructor;
11+
import lombok.AllArgsConstructor;{{ '\nimport lombok.NoArgsConstructor;' if no_args_constructor_required }}
1212
import lombok.Builder;
1313
import lombok.Data;
14-
import lombok.NoArgsConstructor;
15-
1614

1715
@Data
18-
@Builder(toBuilder = true)
19-
@AllArgsConstructor
20-
@NoArgsConstructor
16+
@Builder
17+
@AllArgsConstructor{{ '\n@NoArgsConstructor' if no_args_constructor_required }}
2118
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
2219
public class {{ model_name|uppercase_first_letter }} {
2320
{% for name, type in properties.items() %}

src/main/java/software/amazon/cloudformation/AbstractWrapper.java

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
import software.amazon.cloudformation.resource.Validator;
7171
import software.amazon.cloudformation.resource.exceptions.ValidationException;
7272

73-
public abstract class AbstractWrapper<ResourceT, CallbackT> {
73+
public abstract class AbstractWrapper<ResourceT, CallbackT, ConfigurationT> {
7474

7575
public static final SdkHttpClient HTTP_CLIENT = ApacheHttpClient.builder().build();
7676

@@ -90,7 +90,7 @@ public abstract class AbstractWrapper<ResourceT, CallbackT> {
9090
protected final CloudWatchProvider providerCloudWatchProvider;
9191
protected final CloudWatchLogsProvider cloudWatchLogsProvider;
9292
protected final SchemaValidator validator;
93-
protected final TypeReference<HandlerRequest<ResourceT, CallbackT>> typeReference;
93+
protected final TypeReference<HandlerRequest<ResourceT, CallbackT, ConfigurationT>> typeReference;
9494

9595
protected MetricsPublisher providerMetricsPublisher;
9696

@@ -179,7 +179,7 @@ public void processRequest(final InputStream inputStream, final OutputStream out
179179
TerminalException {
180180

181181
ProgressEvent<ResourceT, CallbackT> handlerResponse = null;
182-
HandlerRequest<ResourceT, CallbackT> request = null;
182+
HandlerRequest<ResourceT, CallbackT, ConfigurationT> request = null;
183183
scrubFiles();
184184
try {
185185
if (inputStream == null) {
@@ -239,7 +239,8 @@ public void processRequest(final InputStream inputStream, final OutputStream out
239239
}
240240

241241
private ProgressEvent<ResourceT, CallbackT>
242-
processInvocation(final JSONObject rawRequest, final HandlerRequest<ResourceT, CallbackT> request) throws IOException,
242+
processInvocation(final JSONObject rawRequest, final HandlerRequest<ResourceT, CallbackT, ConfigurationT> request)
243+
throws IOException,
243244
TerminalException {
244245
assert request != null : "Invalid request object received";
245246

@@ -260,6 +261,7 @@ public void processRequest(final InputStream inputStream, final OutputStream out
260261

261262
// transform the request object to pass to caller
262263
ResourceHandlerRequest<ResourceT> resourceHandlerRequest = transform(request);
264+
ConfigurationT typeConfiguration = request.getRequestData().getTypeConfiguration();
263265

264266
if (resourceHandlerRequest != null) {
265267
resourceHandlerRequest.setPreviousResourceTags(getPreviousResourceTags(request));
@@ -325,7 +327,7 @@ public void processRequest(final InputStream inputStream, final OutputStream out
325327
}
326328

327329
ProgressEvent<ResourceT, CallbackT> handlerResponse = wrapInvocationAndHandleErrors(awsClientProxy,
328-
resourceHandlerRequest, request, callbackContext);
330+
resourceHandlerRequest, request, callbackContext, typeConfiguration);
329331

330332
if (handlerResponse.getStatus() == OperationStatus.IN_PROGRESS && !isMutatingAction) {
331333
throw new TerminalException("READ and LIST handlers must return synchronously.");
@@ -334,8 +336,9 @@ public void processRequest(final InputStream inputStream, final OutputStream out
334336
return handlerResponse;
335337
}
336338

337-
private void
338-
logUnhandledError(final String errorDescription, final HandlerRequest<ResourceT, CallbackT> request, final Throwable e) {
339+
private void logUnhandledError(final String errorDescription,
340+
final HandlerRequest<ResourceT, CallbackT, ConfigurationT> request,
341+
final Throwable e) {
339342
log(String.format("%s in a %s action on a %s: %s%n%s", errorDescription, request.getAction(), request.getResourceType(),
340343
e.toString(), ExceptionUtils.getStackTrace(e)));
341344
}
@@ -349,13 +352,14 @@ public void processRequest(final InputStream inputStream, final OutputStream out
349352
private ProgressEvent<ResourceT, CallbackT>
350353
wrapInvocationAndHandleErrors(final AmazonWebServicesClientProxy awsClientProxy,
351354
final ResourceHandlerRequest<ResourceT> resourceHandlerRequest,
352-
final HandlerRequest<ResourceT, CallbackT> request,
353-
final CallbackT callbackContext) {
355+
final HandlerRequest<ResourceT, CallbackT, ConfigurationT> request,
356+
final CallbackT callbackContext,
357+
final ConfigurationT typeConfiguration) {
354358

355359
Date startTime = Date.from(Instant.now());
356360
try {
357361
ProgressEvent<ResourceT, CallbackT> handlerResponse = invokeHandler(awsClientProxy, resourceHandlerRequest,
358-
request.getAction(), callbackContext);
362+
request.getAction(), callbackContext, typeConfiguration);
359363
if (handlerResponse != null) {
360364
this.log(String.format("Handler returned %s", handlerResponse.getStatus()));
361365
} else {
@@ -384,7 +388,8 @@ public void processRequest(final InputStream inputStream, final OutputStream out
384388

385389
}
386390

387-
protected void writeResponse(final OutputStream outputStream, final ProgressEvent<ResourceT, CallbackT> response)
391+
protected void writeResponse(final OutputStream outputStream,
392+
final ProgressEvent<ResourceT, CallbackT> response)
388393
throws IOException {
389394
if (response.getResourceModel() != null) {
390395
// strip write only properties on final results, we will need the intact model
@@ -437,7 +442,7 @@ private void validateModel(final JSONObject modelObject) throws ValidationExcept
437442
* and is not needed by the handler implementations
438443
* @return A converted ResourceHandlerRequest model
439444
*/
440-
protected abstract ResourceHandlerRequest<ResourceT> transform(HandlerRequest<ResourceT, CallbackT> request)
445+
protected abstract ResourceHandlerRequest<ResourceT> transform(HandlerRequest<ResourceT, CallbackT, ConfigurationT> request)
441446
throws IOException;
442447

443448
/**
@@ -465,14 +470,16 @@ protected abstract ResourceHandlerRequest<ResourceT> transform(HandlerRequest<Re
465470
* {@link Action#DELETE}, {@link Action#READ} {@link Action#LIST} or
466471
* {@link Action#UPDATE}
467472
* @param callbackContext the callback context to handle reentrant calls
473+
* @param typeConfiguration the configuration for the type set by type consumer
468474
* @return progress event indicating success, in progress with delay callback or
469475
* failed state
470476
* @throws Exception propagate any unexpected errors
471477
*/
472478
public abstract ProgressEvent<ResourceT, CallbackT> invokeHandler(AmazonWebServicesClientProxy proxy,
473479
ResourceHandlerRequest<ResourceT> request,
474480
Action action,
475-
CallbackT callbackContext)
481+
CallbackT callbackContext,
482+
ConfigurationT typeConfiguration)
476483
throws Exception;
477484

478485
/*
@@ -512,7 +519,7 @@ private void log(final String message) {
512519
}
513520
}
514521

515-
protected abstract TypeReference<HandlerRequest<ResourceT, CallbackT>> getTypeReference();
522+
protected abstract TypeReference<HandlerRequest<ResourceT, CallbackT, ConfigurationT>> getTypeReference();
516523

517524
protected abstract TypeReference<ResourceT> getModelTypeReference();
518525

@@ -536,7 +543,7 @@ protected void scrubFiles() {
536543
* @return a Map of Tag names to Tag values
537544
*/
538545
@VisibleForTesting
539-
protected Map<String, String> getDesiredResourceTags(final HandlerRequest<ResourceT, CallbackT> request) {
546+
protected Map<String, String> getDesiredResourceTags(final HandlerRequest<ResourceT, CallbackT, ConfigurationT> request) {
540547
Map<String, String> desiredResourceTags = new HashMap<>();
541548
JSONObject object;
542549

@@ -559,7 +566,7 @@ protected Map<String, String> getDesiredResourceTags(final HandlerRequest<Resour
559566
* @return a Map of Tag names to Tag values
560567
*/
561568
@VisibleForTesting
562-
protected Map<String, String> getPreviousResourceTags(final HandlerRequest<ResourceT, CallbackT> request) {
569+
protected Map<String, String> getPreviousResourceTags(final HandlerRequest<ResourceT, CallbackT, ConfigurationT> request) {
563570
Map<String, String> previousResourceTags = new HashMap<>();
564571

565572
if (request != null && request.getRequestData() != null) {
@@ -574,7 +581,7 @@ protected Map<String, String> getPreviousResourceTags(final HandlerRequest<Resou
574581
}
575582

576583
@VisibleForTesting
577-
protected String getStackId(final HandlerRequest<ResourceT, CallbackT> request) {
584+
protected String getStackId(final HandlerRequest<ResourceT, CallbackT, ConfigurationT> request) {
578585
if (request != null) {
579586
return request.getStackId();
580587
}

0 commit comments

Comments
 (0)