Skip to content

Commit 544bb2c

Browse files
authored
Alternate entry point without Lambda (#308)
* Introduce another entrypoint for plugin without lambda dependency * Refactor lambda wrapper and executable wrapper into different classes * Fix spacing and naming * Add more tests and change the way the wrapper outputs info * Change Wrapper.java class, move image config to java plugin * Don't create ExecutableHandlerWrapper when plugin version is not bumped and move start/end response to handlerwrapper.java class instead * Fix merging with master * Add missing constructors * Change ExecutableHandlerWrapper.java to HandlerWrapperExecutable.java for backwards compatible unit tests excludes * Change root logging level to Debug
1 parent 6ff1fd5 commit 544bb2c

25 files changed

+2402
-1539
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ repos:
5858
- id: pytest-local
5959
name: pytest-local
6060
description: Run pytest in the local virtualenv
61-
entry: pytest --cov=rpdk.java --doctest-modules tests/
61+
entry: pytest --cov-report term-missing --cov=rpdk.java --doctest-modules tests/
6262
language: system
6363
# ignore all files, run on hard-coded modules instead
6464
pass_filenames: false

python/rpdk/java/codegen.py

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# pylint: disable=useless-super-delegation,too-many-locals
22
# pylint doesn't recognize abstract methods
33
import logging
4+
import os
45
import shutil
56
import xml.etree.ElementTree as ET # nosec
67
from collections import namedtuple
@@ -44,6 +45,7 @@ def wrapper(*args, **kwargs):
4445
DEFAULT_SETTINGS = {PROTOCOL_VERSION_SETTING: DEFAULT_PROTOCOL_VERSION}
4546

4647
MINIMUM_JAVA_DEPENDENCY_VERSION = "2.0.0"
48+
MINIMUM_JAVA_DEPENDENCY_VERSION_EXECUTABLE_HANDLER_WRAPPER = "2.0.3"
4749

4850

4951
class JavaArchiveNotFoundError(SysExitRecommendedError):
@@ -67,6 +69,7 @@ class JavaLanguagePlugin(LanguagePlugin):
6769
RUNTIME = "java8"
6870
ENTRY_POINT = "{}.HandlerWrapper::handleRequest"
6971
TEST_ENTRY_POINT = "{}.HandlerWrapper::testEntrypoint"
72+
EXECUTABLE_ENTRY_POINT = "{}.HandlerWrapperExecutable"
7073
CODE_URI = "./target/{}-1.0-SNAPSHOT.jar"
7174

7275
def __init__(self):
@@ -166,19 +169,22 @@ def init(self, project):
166169
src = (project.root / "src" / "main" / "java").joinpath(*self.namespace)
167170
LOG.debug("Making source folder structure: %s", src)
168171
src.mkdir(parents=True, exist_ok=True)
172+
resources = project.root / "src" / "resources"
173+
LOG.debug("Making resources folder structure: %s", resources)
174+
resources.mkdir(parents=True, exist_ok=True)
169175
tst = (project.root / "src" / "test" / "java").joinpath(*self.namespace)
170176
LOG.debug("Making test folder structure: %s", tst)
171177
tst.mkdir(parents=True, exist_ok=True)
172178

173179
# initialize shared files
174-
self.init_shared(project, src, tst)
180+
self.init_shared(project, src, tst, resources)
175181

176182
# write specialized generated files
177183
if self._is_aws_guided(project):
178184
self.init_guided_aws(project, src, tst)
179185

180186
@logdebug
181-
def init_shared(self, project, src, tst):
187+
def init_shared(self, project, src, tst, resources):
182188
"""Writing project configuration"""
183189
# .gitignore
184190
path = project.root / ".gitignore"
@@ -257,6 +263,12 @@ def init_shared(self, project, src, tst):
257263
)
258264
project.safewrite(path, contents)
259265

266+
# log4j2
267+
path = resources / "log4j2.xml"
268+
LOG.debug("Writing log4j2: %s", path)
269+
contents = resource_stream(__name__, "data/log4j2.xml").read()
270+
project.safewrite(path, contents)
271+
260272
self.init_handlers(project, src, tst)
261273

262274
@logdebug
@@ -304,6 +316,9 @@ def _init_settings(self, project):
304316
project.runtime = self.RUNTIME
305317
project.entrypoint = self.ENTRY_POINT.format(self.package_name)
306318
project.test_entrypoint = self.TEST_ENTRY_POINT.format(self.package_name)
319+
project.executable_entrypoint = self.EXECUTABLE_ENTRY_POINT.format(
320+
self.package_name
321+
)
307322
project.settings.update(DEFAULT_SETTINGS)
308323

309324
@staticmethod
@@ -345,9 +360,13 @@ def generate(self, project):
345360
package_name=self.package_name,
346361
operations=project.schema.get("handlers", {}).keys(),
347362
pojo_name="ResourceModel",
363+
wrapper_parent="LambdaWrapper",
348364
)
349365
project.overwrite(path, contents)
350366

367+
# write generated handler integration with ExecutableWrapper
368+
self._write_executable_wrapper_class(src, project)
369+
351370
path = src / "BaseConfiguration.java"
352371
LOG.debug("Writing base configuration: %s", path)
353372
template = self.env.get_template("generate/BaseConfiguration.java")
@@ -403,6 +422,39 @@ def generate(self, project):
403422

404423
LOG.debug("Generate complete")
405424

425+
def _write_executable_wrapper_class(self, src, project):
426+
try:
427+
java_plugin_dependency_version = self._get_java_plugin_dependency_version(
428+
project
429+
)
430+
if (
431+
java_plugin_dependency_version
432+
>= MINIMUM_JAVA_DEPENDENCY_VERSION_EXECUTABLE_HANDLER_WRAPPER
433+
):
434+
path = src / "HandlerWrapperExecutable.java"
435+
LOG.debug("Writing handler wrapper: %s", path)
436+
template = self.env.get_template("generate/HandlerWrapper.java")
437+
contents = template.render(
438+
package_name=self.package_name,
439+
operations=project.schema.get("handlers", {}).keys(),
440+
pojo_name="ResourceModel",
441+
wrapper_parent="ExecutableWrapper",
442+
)
443+
project.overwrite(path, contents)
444+
else:
445+
LOG.info(
446+
"Please update your java plugin dependency to version "
447+
"%s or above in order to use "
448+
"the Executable Handler Wrapper feature.",
449+
MINIMUM_JAVA_DEPENDENCY_VERSION_EXECUTABLE_HANDLER_WRAPPER,
450+
)
451+
except JavaPluginNotFoundError:
452+
LOG.info(
453+
"Please make sure to have 'aws-cloudformation-rpdk-java-plugin' "
454+
"to version %s or above.",
455+
MINIMUM_JAVA_DEPENDENCY_VERSION,
456+
)
457+
406458
def _update_settings(self, project):
407459
try:
408460
java_plugin_dependency_version = self._get_java_plugin_dependency_version(
@@ -427,6 +479,15 @@ def _update_settings(self, project):
427479
project.settings[PROTOCOL_VERSION_SETTING] = DEFAULT_PROTOCOL_VERSION
428480
project.write_settings()
429481

482+
if (
483+
hasattr(project, "executable_entrypoint")
484+
and not project.executable_entrypoint
485+
):
486+
project.executable_entrypoint = self.EXECUTABLE_ENTRY_POINT.format(
487+
self.package_name
488+
)
489+
project.write_settings()
490+
430491
@staticmethod
431492
def _find_jar(project):
432493
jar_glob = list(
@@ -490,3 +551,23 @@ def write_with_relative_path(path):
490551
for path in (project.root / "target" / "generated-sources").rglob("*"):
491552
if path.is_file():
492553
write_with_relative_path(path)
554+
555+
@logdebug
556+
def generate_image_build_config(self, project):
557+
"""Generating image build config"""
558+
559+
jar_path = self._find_jar(project)
560+
561+
dockerfile_path = (
562+
os.path.dirname(os.path.realpath(__file__))
563+
+ "/data/build-image-src/Dockerfile-"
564+
+ project.runtime
565+
)
566+
567+
project_path = project.root
568+
569+
return {
570+
"executable_name": str(jar_path.relative_to(project.root)),
571+
"dockerfile_path": dockerfile_path,
572+
"project_path": str(project_path),
573+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
FROM openjdk:11-alpine
2+
ARG executable_name
3+
ADD ${executable_name} handler.jar
4+
ENTRYPOINT ["java", "-Xmx512M", "-cp", "handler.jar"]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
FROM openjdk:8-alpine
2+
ARG executable_name
3+
ADD ${executable_name} handler.jar
4+
ENTRYPOINT ["java", "-Xmx512M", "-cp", "handler.jar"]

python/rpdk/java/data/log4j2.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Configuration status="WARN">
3+
<Appenders>
4+
<Console name="Console" target="SYSTEM_OUT">
5+
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
6+
</Console>
7+
<File name="APPLICATION" fileName="log/application.log">
8+
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
9+
</File>
10+
</Appenders>
11+
<Loggers>
12+
<Root level="DEBUG">
13+
<AppenderRef ref="Console"/>
14+
<AppenderRef ref="APPLICATION"/>
15+
</Root>
16+
</Loggers>
17+
</Configuration>

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

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import software.amazon.awssdk.regions.Region;
88
import software.amazon.cloudformation.Action;
99
import software.amazon.cloudformation.exceptions.BaseHandlerException;
10-
import software.amazon.cloudformation.LambdaWrapper;
10+
import software.amazon.cloudformation.{{ wrapper_parent }};
1111
import software.amazon.cloudformation.loggers.LambdaLogPublisher;
1212
import software.amazon.cloudformation.metrics.MetricsPublisher;
1313
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
@@ -26,18 +26,23 @@
2626
import com.amazonaws.services.lambda.runtime.Context;
2727
import com.fasterxml.jackson.core.type.TypeReference;
2828

29+
import java.io.BufferedReader;
2930
import java.io.IOException;
31+
import java.io.FileOutputStream;
32+
import java.io.FileReader;
3033
import java.io.InputStream;
3134
import java.io.OutputStream;
35+
import java.io.PrintStream;
3236
import java.nio.charset.Charset;
3337
import java.util.HashMap;
3438
import java.util.Map;
39+
import java.util.UUID;
3540

3641
import org.apache.commons.io.IOUtils;
3742
import org.json.JSONObject;
3843

3944

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

4247
private final Configuration configuration = new Configuration();
4348
private JSONObject resourceSchema;
@@ -50,7 +55,7 @@ public final class HandlerWrapper extends LambdaWrapper<{{ pojo_name }}, Callbac
5055
new TypeReference<ResourceHandlerTestPayload<{{ pojo_name }}, CallbackContext>>() {};
5156

5257

53-
public HandlerWrapper() {
58+
public {{ "HandlerWrapper" if wrapper_parent == "LambdaWrapper" else "HandlerWrapperExecutable" }}() {
5459
initialiseHandlers();
5560
}
5661

@@ -62,11 +67,10 @@ private void initialiseHandlers() {
6267

6368
@Override
6469
public ProgressEvent<{{ pojo_name }}, CallbackContext> invokeHandler(
65-
final AmazonWebServicesClientProxy proxy,
66-
final ResourceHandlerRequest<{{ pojo_name }}> request,
67-
final Action action,
68-
final CallbackContext callbackContext) {
69-
70+
final AmazonWebServicesClientProxy proxy,
71+
final ResourceHandlerRequest<{{ pojo_name }}> request,
72+
final Action action,
73+
final CallbackContext callbackContext) {
7074
final String actionName = (action == null) ? "<null>" : action.toString(); // paranoia
7175
if (!handlers.containsKey(action))
7276
throw new RuntimeException("Unknown action " + actionName);
@@ -79,6 +83,9 @@ private void initialiseHandlers() {
7983
return result;
8084
}
8185

86+
{% if wrapper_parent == "LambdaWrapper" -%}
87+
88+
8289
public void testEntrypoint(
8390
final InputStream inputStream,
8491
final OutputStream outputStream,
@@ -108,8 +115,37 @@ public void testEntrypoint(
108115
response = ProgressEvent.defaultFailureHandler(e, HandlerErrorCode.InternalFailure);
109116
} finally {
110117
writeResponse(outputStream, response);
118+
outputStream.close();
119+
}
120+
}
121+
{% else %}
122+
public static void main(String[] args) throws IOException {
123+
if (args.length != 1){
124+
System.exit(1);
125+
}
126+
final String outputFile = (UUID.randomUUID().toString() + ".txt");
127+
try(FileOutputStream output = new FileOutputStream(outputFile)){
128+
try(InputStream input=IOUtils.toInputStream(args[0],"UTF-8")){
129+
new HandlerWrapperExecutable().handleRequest(input, output);
130+
output.flush();
131+
}
132+
}
133+
System.out.println("__CFN_RESOURCE_START_RESPONSE__");
134+
readFileToSystemOut(outputFile);
135+
System.out.println("__CFN_RESOURCE_END_RESPONSE__");
136+
}
137+
138+
private static void readFileToSystemOut(final String fileName) throws IOException {
139+
//Create object of FileReader
140+
final FileReader inputFile = new FileReader(fileName);
141+
try(BufferedReader bufferReader = new BufferedReader(inputFile)) {
142+
String line;
143+
while ((line = bufferReader.readLine()) != null) {
144+
System.out.println(line);
145+
}
111146
}
112147
}
148+
{%- endif %}
113149

114150
@Override
115151
public JSONObject provideResourceSchemaJSONObject() {

python/rpdk/java/templates/init/shared/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,24 @@
3232
<version>1.18.4</version>
3333
<scope>provided</scope>
3434
</dependency>
35+
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
36+
<dependency>
37+
<groupId>org.apache.logging.log4j</groupId>
38+
<artifactId>log4j-api</artifactId>
39+
<version>2.13.3</version>
40+
</dependency>
41+
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
42+
<dependency>
43+
<groupId>org.apache.logging.log4j</groupId>
44+
<artifactId>log4j-core</artifactId>
45+
<version>2.13.3</version>
46+
</dependency>
47+
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
48+
<dependency>
49+
<groupId>org.apache.logging.log4j</groupId>
50+
<artifactId>log4j-slf4j-impl</artifactId>
51+
<version>2.13.3</version>
52+
</dependency>
3553

3654
<!-- https://mvnrepository.com/artifact/org.assertj/assertj-core -->
3755
<dependency>

0 commit comments

Comments
 (0)