From b60c1ab4ed40fe7984089951a4d6bef96003925b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaras=C5=82a=C5=AD=20Viktor=C4=8Dyk?= Date: Thu, 1 Apr 2021 14:16:37 +0000 Subject: [PATCH] Allow passing HandlerFunction to run directly --- src/Common/index.ts | 4 + src/index.ts | 14 ++- .../codebuild/buildspec.os.alpine.4.yml | 102 ++++++++++++++++++ .../docker/Dockerfile.programmatic.alpine | 52 +++++++++ .../test-handlers/programmatic/index.js | 8 ++ .../test-handlers/programmatic/package.json | 11 ++ test/unit/Common/Common.test.ts | 21 ++++ 7 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 test/integration/codebuild/buildspec.os.alpine.4.yml create mode 100644 test/integration/docker/Dockerfile.programmatic.alpine create mode 100644 test/integration/test-handlers/programmatic/index.js create mode 100644 test/integration/test-handlers/programmatic/package.json create mode 100644 test/unit/Common/Common.test.ts diff --git a/src/Common/index.ts b/src/Common/index.ts index 0076330..a2d6459 100644 --- a/src/Common/index.ts +++ b/src/Common/index.ts @@ -78,3 +78,7 @@ export type HandlerFunction = ( data: IEnvironmentData & IHeaderData, callback: CallbackFunction ) => PromiseLike | unknown; + +export function isHandlerFunction(value: any): value is HandlerFunction { + return typeof value === "function"; +} diff --git a/src/index.ts b/src/index.ts index 336c1ca..148edad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ "use strict"; -import { HandlerFunction } from "./Common"; +import { HandlerFunction, isHandlerFunction } from "./Common"; import * as Errors from "./Errors"; import RuntimeClient from "./RuntimeClient"; import Runtime from "./Runtime"; @@ -18,7 +18,13 @@ import * as UserFunction from "./utils/UserFunction"; LogPatch.patchConsole(); -export function run(appRoot: string, handler: string): void { +export function run(appRoot: string, handler: string): void; +export function run(handler: HandlerFunction): void; + +export function run( + appRootOrHandler: string | HandlerFunction, + handler: string = "" +): void { if (!process.env.AWS_LAMBDA_RUNTIME_API) { throw new Error("Missing Runtime API Server configuration."); } @@ -50,7 +56,9 @@ export function run(appRoot: string, handler: string): void { BeforeExitListener.reset(); process.on("beforeExit", BeforeExitListener.invoke); - const handlerFunc = UserFunction.load(appRoot, handler) as HandlerFunction; + const handlerFunc = isHandlerFunction(appRootOrHandler) + ? appRootOrHandler + : (UserFunction.load(appRootOrHandler, handler) as HandlerFunction); const runtime = new Runtime(client, handlerFunc, errorCallbacks); runtime.scheduleIteration(); diff --git a/test/integration/codebuild/buildspec.os.alpine.4.yml b/test/integration/codebuild/buildspec.os.alpine.4.yml new file mode 100644 index 0000000..b02dbeb --- /dev/null +++ b/test/integration/codebuild/buildspec.os.alpine.4.yml @@ -0,0 +1,102 @@ +version: 0.2 + +env: + variables: + OS_DISTRIBUTION: alpine + NODE_BINARY_LOCATION: "/usr/local/bin/node" + NPX_BINARY_LOCATION: "/usr/local/bin/npx" +batch: + build-matrix: + static: + ignore-failure: false + env: + type: LINUX_CONTAINER + privileged-mode: true + dynamic: + env: + variables: + DISTRO_VERSION: + - "3.12" + RUNTIME_VERSION: + - "12" + - "14" +phases: + pre_build: + commands: + - export IMAGE_TAG="nodejs-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" + - echo "Extracting and including the Runtime Interface Emulator" + - SCRATCH_DIR=".scratch" + - mkdir "${SCRATCH_DIR}" + - tar -xvf test/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}" + - > + cp "test/integration/docker/Dockerfile.programmatic.${OS_DISTRIBUTION}" \ + "${SCRATCH_DIR}/Dockerfile.programmatic.${OS_DISTRIBUTION}.tmp" + - > + echo "RUN apk add curl" >> \ + "${SCRATCH_DIR}/Dockerfile.programmatic.${OS_DISTRIBUTION}.tmp" + - > + echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \ + "${SCRATCH_DIR}/Dockerfile.programmatic.${OS_DISTRIBUTION}.tmp" + - > + if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; + then + echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." + else + echo "Performing DockerHub login . . ." + docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD + fi + - echo "Building image ${IMAGE_TAG}" + - > + docker build . \ + -f "${SCRATCH_DIR}/Dockerfile.programmatic.${OS_DISTRIBUTION}.tmp" \ + -t "${IMAGE_TAG}" \ + --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ + --build-arg DISTRO_VERSION="${DISTRO_VERSION}" + build: + commands: + - set -x + - echo "Running Image ${IMAGE_TAG}" + - docker network create "${OS_DISTRIBUTION}-network" + - > + docker run \ + --detach \ + -e "NODE_BINARY_LOCATION=${NODE_BINARY_LOCATION}" \ + --name "${OS_DISTRIBUTION}-app" \ + --network "${OS_DISTRIBUTION}-network" \ + --entrypoint="" \ + "${IMAGE_TAG}" \ + sh -c '/usr/bin/aws-lambda-rie ${NODE_BINARY_LOCATION} index.js' + - sleep 2 + - > + docker run \ + --name "${OS_DISTRIBUTION}-tester" \ + --env "TARGET=${OS_DISTRIBUTION}-app" \ + --network "${OS_DISTRIBUTION}-network" \ + --entrypoint="" \ + "${IMAGE_TAG}" \ + sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' + - actual="$(docker logs --tail 1 "${OS_DISTRIBUTION}-tester" | xargs)" + - expected='success' + - | + echo "Response: ${actual}" + if [[ "$actual" != "$expected" ]]; then + echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" + echo "---------Container Logs: ${OS_DISTRIBUTION}-app----------" + echo + docker logs "${OS_DISTRIBUTION}-app" + echo + echo "---------------------------------------------------" + echo "--------Container Logs: ${OS_DISTRIBUTION}-tester--------" + echo + docker logs "${OS_DISTRIBUTION}-tester" + echo + echo "---------------------------------------------------" + exit -1 + fi + finally: + - echo "Cleaning up..." + - docker stop "${OS_DISTRIBUTION}-app" || true + - docker rm --force "${OS_DISTRIBUTION}-app" || true + - docker stop "${OS_DISTRIBUTION}-tester" || true + - docker rm --force "${OS_DISTRIBUTION}-tester" || true + - docker network rm "${OS_DISTRIBUTION}-network" || true diff --git a/test/integration/docker/Dockerfile.programmatic.alpine b/test/integration/docker/Dockerfile.programmatic.alpine new file mode 100644 index 0000000..6bb1e80 --- /dev/null +++ b/test/integration/docker/Dockerfile.programmatic.alpine @@ -0,0 +1,52 @@ +# Define global args +ARG FUNCTION_DIR="/home/app/" +ARG RUNTIME_VERSION +ARG DISTRO_VERSION + +# Stage 1 - build function and dependencies +FROM node:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} AS build-image +# Install aws-lambda-cpp build dependencies +RUN apk add --update-cache \ + build-base \ + libtool \ + musl-dev \ + libressl-dev \ + libffi-dev \ + autoconf \ + automake \ + libexecinfo-dev \ + make \ + cmake \ + python3 \ + libcurl + +# Include global arg in this stage of the build +ARG FUNCTION_DIR +# Create function directory +RUN mkdir -p ${FUNCTION_DIR} + +# Copy & build Runtime Interface Client package (as we're installing it from a local filesystem source) +WORKDIR ${FUNCTION_DIR}/deps/aws-lambda-ric +COPY . . +RUN make build && \ + npm run test:unit + +# Copy function code +COPY test/integration/test-handlers/programmatic/* ${FUNCTION_DIR} +# Install the function's dependencies +WORKDIR ${FUNCTION_DIR} +RUN npm install + + +# Stage 2 - final runtime image +# Grab a fresh copy of the Node image +FROM node:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} + +# Include global arg in this stage of the build +ARG FUNCTION_DIR +# Set working directory to function root directory +WORKDIR ${FUNCTION_DIR} +# Copy in the built dependencies +COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} + +CMD [ "/usr/local/bin/node", "index.js" ] diff --git a/test/integration/test-handlers/programmatic/index.js b/test/integration/test-handlers/programmatic/index.js new file mode 100644 index 0000000..7f23f49 --- /dev/null +++ b/test/integration/test-handlers/programmatic/index.js @@ -0,0 +1,8 @@ +const ric = require('aws-lambda-ric'); + +const echo = async (event, context) => { + console.log('hello world'); + return 'success'; +}; + +ric.run(echo); diff --git a/test/integration/test-handlers/programmatic/package.json b/test/integration/test-handlers/programmatic/package.json new file mode 100644 index 0000000..c15911e --- /dev/null +++ b/test/integration/test-handlers/programmatic/package.json @@ -0,0 +1,11 @@ +{ + "name": "programmatic-hanlder", + "version": "1.0.0", + "description": "Sample Lambda echo handler for NodeJS", + "main": "app.js", + "author": "AWS Lambda", + "license": "Apache-2.0", + "dependencies": { + "aws-lambda-ric": "file:deps/aws-lambda-ric" + } +} diff --git a/test/unit/Common/Common.test.ts b/test/unit/Common/Common.test.ts new file mode 100644 index 0000000..eeeae21 --- /dev/null +++ b/test/unit/Common/Common.test.ts @@ -0,0 +1,21 @@ +"use strict"; + +require("should"); +import * as Common from "../../../src/Common"; + +describe("type guards HandlerFunction", () => { + it("should compile the code", () => { + const func = () => {}; + if (Common.isHandlerFunction(func)) { + func(); + } + }); + + it("should return true if function", () => { + Common.isHandlerFunction(() => {}).should.be.true(); + }); + + it("should return false if not function", () => { + Common.isHandlerFunction("MyHandler").should.be.false(); + }); +});