├── .dockerignore ├── .eslintignore ├── .eslintrc ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── test-on-push-and-pr.yml ├── .gitignore ├── .npmignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── RELEASE.CHANGELOG.md ├── THIRD-PARTY-LICENSES ├── bin └── index.mjs ├── binding.gyp ├── deps ├── aws-lambda-cpp-0.2.8.tar.gz ├── curl-7_83_1.tar.gz ├── patches │ ├── 0001-curl-disable_wakeup.patch │ ├── aws-lambda-cpp-add-tenant-id.patch │ ├── aws-lambda-cpp-add-xray-response.patch │ └── libcurl-configure-template.patch └── versions ├── package-lock.json ├── package.json ├── scripts ├── postinstall.sh ├── preinstall.sh └── update_dependencies.sh ├── src ├── BeforeExitListener.js ├── CallbackContext.js ├── Errors.js ├── HttpResponseStream.js ├── InvokeContext.js ├── LogPatch.js ├── NativeModuleLoader.js ├── RAPIDClient.js ├── ResponseStream.js ├── Runtime.js ├── StreamingContext.js ├── UserFunction.js ├── VerboseLog.js ├── XRayError.js ├── build.js ├── index.mjs ├── rapid-client.cc └── types │ ├── awslambda.d.ts │ └── index.d.mts └── test ├── handlers ├── asyncInit.mjs ├── asyncInitRejection.mjs ├── async_init_package │ ├── cjsModuleInEsmPackage.js │ ├── index.js │ ├── nested │ │ ├── even │ │ │ ├── more │ │ │ │ └── index.js │ │ │ ├── package.json │ │ │ └── readme.txt │ │ └── index.js │ └── package.json ├── async_init_type_not_module │ ├── index.js │ └── package.json ├── async_init_with_node_modules │ ├── node_modules │ │ └── index.js │ └── package.json ├── beforeExitBehaviour.js ├── cjsModule.cjs ├── core.js ├── defaultHandler.js ├── defaultHandlerESM.mjs ├── esModule.mjs ├── esModuleImports.mjs ├── extensionless │ ├── esm-extensionless │ └── index ├── nestedHandler.js ├── package-lock.json ├── package.json ├── pkg-less │ ├── cjsAndMjs.js │ ├── cjsImportCjs.js │ ├── cjsImportESM.cjs │ ├── cjsInMjs.mjs │ ├── cjsModule.cjs │ ├── esmImportCjs.mjs │ ├── esmInCjs.cjs │ ├── esmModule.js │ └── esmRequireCjs.mjs ├── pkg │ ├── type-cjs │ │ ├── cjs │ │ ├── cjsModule.js │ │ ├── esm │ │ ├── esmModule.js │ │ └── package.json │ └── type-esm │ │ ├── cjs │ │ ├── cjsModule.js │ │ ├── esm │ │ ├── esmModule.js │ │ └── package.json ├── precedence ├── precedence.js ├── precedence.json ├── precedence.mjs ├── precedence.node ├── precedenceJsVsMjs.js ├── precedenceJsVsMjs.mjs ├── precedenceMjsVsCjs.cjs └── precedenceMjsVsCjs.mjs ├── integration ├── codebuild-local │ ├── Dockerfile.agent │ ├── codebuild_build.sh │ ├── test_all.sh │ └── test_one.sh ├── codebuild │ ├── buildspec.os.alpine.1.yml │ ├── buildspec.os.alpine.2.yml │ ├── buildspec.os.alpine.3.yml │ ├── buildspec.os.amazonlinux.2.yml │ ├── buildspec.os.amazonlinux.2023.yml │ ├── buildspec.os.centos.yml │ ├── buildspec.os.debian.1.yml │ ├── buildspec.os.debian.2.yml │ ├── buildspec.os.ubuntu.1.yml │ └── buildspec.os.ubuntu.2.yml ├── docker │ ├── Dockerfile.echo.alpine │ ├── Dockerfile.echo.amazonlinux │ ├── Dockerfile.echo.centos │ ├── Dockerfile.echo.debian │ ├── Dockerfile.echo.ubuntu │ └── Dockerfile.programmatic.alpine ├── resources │ ├── aws-lambda-rie-arm64.tar.gz │ └── aws-lambda-rie.tar.gz └── test-handlers │ ├── echo │ ├── index.js │ └── package.json │ └── programmatic │ ├── index.mjs │ └── package.json ├── unit ├── Dockerfile.nodejs18.x ├── Dockerfile.nodejs20.x ├── Dockerfile.nodejs22.x ├── ErrorsTest.js ├── FakeTelemetryTarget.js ├── InvokeContextTest.js ├── LogPatchTest.js ├── LoggingGlobals.js ├── RAPIDClientTest.js ├── ResponseStreamTest.js ├── StreamingContextTest.js ├── UserFunctionTest.js └── package.json └── util └── StdoutReporter.test.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | 4 | # Required for the async_init_with_node_modules unit test 5 | !test/handlers/async_init_with_node_modules/node_modules 6 | 7 | build 8 | **/build 9 | 10 | dist 11 | **/dist 12 | 13 | .idea 14 | **/.idea 15 | 16 | package-lock.json 17 | **/package-lock.json 18 | 19 | .git 20 | .DS_STORE -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/node_modules 3 | 4 | async_init_package 5 | test-handlers 6 | 7 | dist/** -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2022 4 | }, 5 | "extends": [ 6 | "plugin:prettier/recommended" 7 | ], 8 | "env": { 9 | "node": true, 10 | "mocha": true, 11 | "es6": true 12 | }, 13 | "rules": { 14 | "strict": [ 15 | "error", 16 | "global" 17 | ], 18 | "indent": [ 19 | "error", 20 | 2 21 | ], 22 | "camelcase": "error", 23 | "no-console": "off", 24 | "no-unused-vars": [ 25 | "error", 26 | { 27 | "argsIgnorePattern": "^_" 28 | } 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | _Issue #, if available:_ 2 | 3 | _Description of changes:_ 4 | 5 | _Target (OCI, Managed Runtime, both):_ 6 | 7 | ## Checklist 8 | - [ ] I have run `npm install` to generate the `package-lock.json` correctly and included it in the PR. 9 | 10 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: "npm" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | - package-ecosystem: "npm" 9 | directory: "/test/handlers" 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/test-on-push-and-pr.yml: -------------------------------------------------------------------------------- 1 | name: test-on-push-and-pr 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ '*' ] 8 | 9 | jobs: 10 | unit-test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | node-version: [18, 20, 22] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Build and run tests for Node.js ${{ matrix.node-version }} 20 | run: | 21 | docker build -f test/unit/Dockerfile.nodejs${{ matrix.node-version }}.x -t unit/nodejs.${{ matrix.node-version }}x . 22 | docker run unit/nodejs.${{ matrix.node-version }}x 23 | 24 | integration-test: 25 | runs-on: ubuntu-latest 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | distro: [alpine, amazonlinux, centos, debian, ubuntu] 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | - name: Run ${{ matrix.distro }} integration tests 34 | run: DISTRO=${{ matrix.distro }} make test-integ 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .DS_Store 3 | 4 | test/integration/resources/init 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | *.lcov 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Compiled binary addons (https://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules/ 32 | 33 | # Required for the async_init_with_node_modules unit test 34 | !test/handlers/async_init_with_node_modules/node_modules 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional eslint cache 40 | .eslintcache 41 | .eslintignore 42 | 43 | # Output of 'npm pack' 44 | *.tgz 45 | 46 | # Build 47 | dist/ 48 | 49 | # Stores VSCode versions used for testing VSCode extensions 50 | .vscode-test 51 | 52 | deps/artifacts/ 53 | deps/aws-lambda-cpp*/ 54 | deps/curl*/ 55 | 56 | # Local codebuild 57 | codebuild.*/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build 2 | .DS_Store 3 | 4 | src/* 5 | # Rapid-client.c to be used with node-gyp 6 | !src/rapid-client.cc 7 | test 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | 14 | # IDEs 15 | .idea 16 | *.iml 17 | 18 | # Diagnostic reports (https://nodejs.org/api/report.html) 19 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 20 | 21 | # Runtime data 22 | pids 23 | *.pid 24 | *.seed 25 | *.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | *.lcov 33 | 34 | # nyc test coverage 35 | .nyc_output 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint config & cache 47 | .eslintrc 48 | .eslintcache 49 | .eslintignore 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Stores VSCode versions used for testing VSCode extensions 55 | .vscode-test 56 | 57 | # git 58 | .git* 59 | 60 | # Docker 61 | .dockerignore 62 | 63 | Makefile -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides some additional documentation on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | target: 2 | $(info ${HELP_MESSAGE}) 3 | @exit 0 4 | 5 | init: 6 | npm install 7 | 8 | test: 9 | npm run test 10 | 11 | setup-codebuild-agent: 12 | docker build -t codebuild-agent - < test/integration/codebuild-local/Dockerfile.agent 13 | 14 | test-smoke: setup-codebuild-agent 15 | CODEBUILD_IMAGE_TAG=codebuild-agent test/integration/codebuild-local/test_one.sh test/integration/codebuild/buildspec.os.alpine.1.yml alpine 3.16 18 16 | 17 | test-integ: setup-codebuild-agent 18 | CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" test/integration/codebuild-local/test_all.sh test/integration/codebuild 19 | 20 | copy-files: 21 | npm run copy-files 22 | 23 | install: 24 | BUILD=$(BUILD) npm install 25 | 26 | format: 27 | npm run format 28 | 29 | # Command to run everytime you make changes to verify everything works 30 | dev: init test 31 | 32 | # Verifications to run before sending a pull request 33 | pr: build dev test-smoke 34 | 35 | clean: 36 | npm run clean 37 | 38 | build: copy-files 39 | make install BUILD=1 40 | npm run build 41 | 42 | pack: build 43 | npm pack 44 | 45 | .PHONY: target init test setup-codebuild-agent test-smoke test-integ install format dev pr clean build pack copy-files 46 | 47 | define HELP_MESSAGE 48 | 49 | Usage: $ make [TARGETS] 50 | 51 | TARGETS 52 | format Run format to automatically update your code to match our formatting. 53 | build Builds the package. 54 | clean Cleans the working directory by removing built artifacts. 55 | dev Run all development tests after a change. 56 | init Initialize and install the dependencies and dev-dependencies for this project. 57 | pr Perform all checks before submitting a Pull Request. 58 | test Run the Unit tests. 59 | 60 | endef 61 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AWS Lambda NodeJS Runtime Interface Client 2 | 3 | We have open-sourced a set of software packages, Runtime Interface Clients (RIC), that implement the Lambda 4 | [Runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html), allowing you to seamlessly extend your preferred 5 | base images to be Lambda compatible. 6 | The Lambda Runtime Interface Client is a lightweight interface that allows your runtime to receive requests from and send requests to the Lambda service. 7 | 8 | The Lambda NodeJS Runtime Interface Client is vended through [npm](https://www.npmjs.com/package/aws-lambda-ric). 9 | You can include this package in your preferred base image to make that base image Lambda compatible. 10 | 11 | ## Requirements 12 | The NodeJS Runtime Interface Client package currently supports NodeJS versions: 13 | - 16.x 14 | - 18.x 15 | - 20.x 16 | 17 | ## Usage 18 | 19 | ### Creating a Docker Image for Lambda with the Runtime Interface Client 20 | First step is to choose the base image to be used. The supported Linux OS distributions are: 21 | 22 | - Amazon Linux (2 and 2023) 23 | - Alpine 24 | - CentOS 25 | - Debian 26 | - Ubuntu 27 | 28 | The Runtime Interface Client can be installed outside of the Dockerfile as a dependency of the function we want to run in Lambda (run the below command in your function directory to add the dependency to `package.json`): 29 | ```shell script 30 | npm install aws-lambda-ric --save 31 | ``` 32 | or inside the Dockerfile: 33 | ```dockerfile 34 | RUN npm install aws-lambda-ric 35 | ``` 36 | 37 | Next step would be to copy your Lambda function code into the image's working directory. 38 | ```dockerfile 39 | # Copy function code 40 | RUN mkdir -p ${FUNCTION_DIR} 41 | COPY myFunction/* ${FUNCTION_DIR} 42 | 43 | WORKDIR ${FUNCTION_DIR} 44 | 45 | # If the dependency is not in package.json uncomment the following line 46 | # RUN npm install aws-lambda-ric 47 | 48 | RUN npm install 49 | ``` 50 | 51 | The next step would be to set the `ENTRYPOINT` property of the Docker image to invoke the Runtime Interface Client and then set the `CMD` argument to specify the desired handler. 52 | 53 | Example Dockerfile (to keep the image light we used a multi-stage build): 54 | ```dockerfile 55 | # Define custom function directory 56 | ARG FUNCTION_DIR="/function" 57 | 58 | FROM node:18-buster as build-image 59 | 60 | # Include global arg in this stage of the build 61 | ARG FUNCTION_DIR 62 | 63 | # Install aws-lambda-cpp build dependencies 64 | RUN apt-get update && \ 65 | apt-get install -y \ 66 | g++ \ 67 | make \ 68 | cmake \ 69 | unzip \ 70 | libcurl4-openssl-dev 71 | 72 | # Copy function code 73 | RUN mkdir -p ${FUNCTION_DIR} 74 | COPY myFunction/* ${FUNCTION_DIR} 75 | 76 | WORKDIR ${FUNCTION_DIR} 77 | 78 | RUN npm install 79 | 80 | # If the dependency is not in package.json uncomment the following line 81 | # RUN npm install aws-lambda-ric 82 | 83 | # Grab a fresh slim copy of the image to reduce the final size 84 | FROM node:18-buster-slim 85 | 86 | # Required for Node runtimes which use npm@8.6.0+ because 87 | # by default npm writes logs under /home/.npm and Lambda fs is read-only 88 | ENV NPM_CONFIG_CACHE=/tmp/.npm 89 | 90 | # Include global arg in this stage of the build 91 | ARG FUNCTION_DIR 92 | 93 | # Set working directory to function root directory 94 | WORKDIR ${FUNCTION_DIR} 95 | 96 | # Copy in the built dependencies 97 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 98 | 99 | ENTRYPOINT ["/usr/local/bin/npx", "aws-lambda-ric"] 100 | CMD ["app.handler"] 101 | ``` 102 | 103 | Example NodeJS handler `app.js`: 104 | ```js 105 | "use strict"; 106 | 107 | exports.handler = async (event, context) => { 108 | return 'Hello World!'; 109 | } 110 | ``` 111 | 112 | ### Local Testing 113 | 114 | To make it easy to locally test Lambda functions packaged as container images we open-sourced a lightweight web-server, Lambda Runtime Interface Emulator (RIE), which allows your function packaged as a container image to accept HTTP requests. You can install the [AWS Lambda Runtime Interface Emulator](https://github.com/aws/aws-lambda-runtime-interface-emulator) on your local machine to test your function. Then when you run the image function, you set the entrypoint to be the emulator. 115 | 116 | *To install the emulator and test your Lambda function* 117 | 118 | 1) From your project directory, run the following command to download the RIE from GitHub and install it on your local machine. 119 | 120 | ```shell script 121 | mkdir -p ~/.aws-lambda-rie && \ 122 | curl -Lo ~/.aws-lambda-rie/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie && \ 123 | chmod +x ~/.aws-lambda-rie/aws-lambda-rie 124 | ``` 125 | 2) Run your Lambda image function using the docker run command. 126 | 127 | ```shell script 128 | docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \ 129 | --entrypoint /aws-lambda/aws-lambda-rie \ 130 | myfunction:latest \ 131 | /usr/local/bin/npx aws-lambda-ric app.handler 132 | ``` 133 | 134 | This runs the image as a container and starts up an endpoint locally at `http://localhost:9000/2015-03-31/functions/function/invocations`. 135 | 136 | 3) Post an event to the following endpoint using a curl command: 137 | 138 | ```shell script 139 | curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}' 140 | ``` 141 | 142 | This command invokes the function running in the container image and returns a response. 143 | 144 | *Alternately, you can also include RIE as a part of your base image. See the AWS documentation on how to [Build RIE into your base image](https://docs.aws.amazon.com/lambda/latest/dg/images-test.html#images-test-alternative).* 145 | 146 | 147 | ## Development 148 | 149 | ### Building the package 150 | Clone this repository and run: 151 | 152 | ```shell script 153 | make init 154 | make build 155 | ``` 156 | 157 | ### Running tests 158 | 159 | Make sure the project is built: 160 | ```shell script 161 | make init build 162 | ``` 163 | Then, 164 | * to run unit tests: `make test` 165 | * to run integration tests: `make test-integ` 166 | * to run smoke tests: `make test-smoke` 167 | 168 | ### Raising a PR 169 | When modifying dependencies (`package.json`), make sure to: 170 | 1. Run `npm install` to generate an updated `package-lock.json` 171 | 2. Commit both `package.json` and `package-lock.json` together 172 | 173 | We require package-lock.json to be checked in to ensure consistent installations across development environments. 174 | 175 | ### Troubleshooting 176 | 177 | While running integration tests, you might encounter the Docker Hub rate limit error with the following body: 178 | ``` 179 | You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limits 180 | ``` 181 | To fix the above issue, consider authenticating to a Docker Hub account by setting the Docker Hub credentials as below CodeBuild environment variables. 182 | ```shell script 183 | DOCKERHUB_USERNAME= 184 | DOCKERHUB_PASSWORD= 185 | ``` 186 | Recommended way is to set the Docker Hub credentials in CodeBuild job by retrieving them from AWS Secrets Manager. 187 | ## Security 188 | 189 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 190 | 191 | ## License 192 | 193 | This project is licensed under the Apache-2.0 License. 194 | -------------------------------------------------------------------------------- /RELEASE.CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### May 21, 2025 2 | `3.3.0` 3 | - Add support for multi tenancy ([#128](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/128)) 4 | 5 | ### Aug 26, 2024 6 | `3.2.1` 7 | - Update test dependencies ([#115](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/115)) 8 | - Fix autoconf build issue ([#117](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/117)) 9 | 10 | ### Jul 03, 2024 11 | `3.2.0` 12 | - Introduce advanced logging controls ([#91](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/91)) 13 | - Bump package-lock deps ([#98](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/98)) 14 | - Remove Node14 from integ tests matrix since it is deprecated ([#99](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/99)) 15 | - Bump tar from 6.1.15 to 6.2.1 ([#103](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/103)) 16 | - Handle invalid char when sending HTTP request to Runtime API ([#100](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/100)) 17 | - Bump braces from 3.0.2 to 3.0.3 ([#109](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/109)) 18 | - Update codebuild_build.sh script ([#110](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/110)) 19 | - Fix centos and ubuntu integ tests ([#111](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/111)) 20 | - Encode request id in URI path ([#113](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/113)) 21 | - Release aws-lambda-ric 3.2.0 ([#114](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/114)) 22 | 23 | ### Nov 09, 2023 24 | `3.1.0` 25 | - tar using --no-same-owner by @JavaScriptBach ([#46](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/46)) 26 | - Use python3.8 in al2 integ tests ([#72](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/72)) 27 | - Create pull request template ([#73](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/73)) 28 | - Bump deps ([#79](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/79)) 29 | - Remove unrecognized --disable-websockets option ([#80](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/80)) 30 | - Update Distros and integration tests ([#82](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/82)) 31 | - Clean up images after running integ tests ([#84](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/84)) 32 | - Add Alpine3.17,3.18 remove 3.15 for integ tests ([#85](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/85)) 33 | - Bump @babel/traverse from 7.22.5 to 7.23.2 ([#86](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/86)) 34 | - Add Node20 to the test matrix ([#87](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/87)) 35 | - Release aws-lambda-ric 3.1.0 ([#88](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/88)) 36 | 37 | ### Jun 26, 2023 38 | `3.0.0` 39 | - AWS Lambda response streaming support 40 | - ES module support 41 | - Migrate from TypeScript to JavaScript, Include type declaration files for TypeScript support. 42 | - Support Amazon Linux 2023 43 | - Update RIE to v1.12 44 | - Reduce image size by deleting aws-lambda-cpp and curl dependencies after building them 45 | - aws-lambda-ric 3.0.0 release ([#70](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/70)) 46 | - Run integration tests against every distro on PR ([#71](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/71)) 47 | 48 | 49 | ### May 15, 2023 50 | `2.1.0` 51 | - Allow passing HandlerFunction to run function directly ([#20](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/20)) 52 | - Update dependencies: tar and ansi-regex ([#38](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/38)) 53 | - Bump minimist from 1.2.5 to 1.2.6 ([#48](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/48)) 54 | - Update Curl to 7.83.0 ([#49](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/49)) 55 | - Update Curl to 7.84.0 ([#52](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/52)) 56 | - update aws-lambda-cpp ([#57](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/57)) 57 | - Bump minimatch and mocha ([#58](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/58)) 58 | - Update dependencies and distros ([#65](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/65)) 59 | - Revert libcurl 7.84.0 update ([#66](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/66)) 60 | - Stage aws-lambda-ric 2.1.0 release ([#67](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/67)) 61 | 62 | ### Sep 29, 2021 63 | `2.0.0` 64 | - AWS Lambda Runtime Interface Client for NodeJS with ARM64 support 65 | 66 | ### Jun 09, 2021 67 | `1.1.0` 68 | - Update Curl version to 7.77.0 ([#23](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/23)) 69 | - Update dependencies, remove unused dependencies, add prettier plugin to eslint ([#19](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/19)) 70 | - Fix errors issues 71 | - Remove trailing . from sample curl command 72 | - Add `docker login` to fix pull rate limit issue ([#2](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/2)) 73 | - Include GitHub action on push and pr ([#1](https://github.com/aws/aws-lambda-nodejs-runtime-interface-client/pull/1)) 74 | 75 | ### Dec 01, 2020 76 | `1.0.0` 77 | - Initial release of AWS Lambda Runtime Interface Client for NodeJS 78 | 79 | -------------------------------------------------------------------------------- /bin/index.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 3 | 4 | import { run } from "../dist/index.mjs" 5 | 6 | if (process.argv.length < 3) { 7 | throw new Error("No handler specified"); 8 | } 9 | 10 | const appRoot = process.cwd(); 11 | const handler = process.argv[2]; 12 | 13 | console.log(`Executing '${handler}' in function directory '${appRoot}'`); 14 | await run(appRoot, handler); -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'rapid-client', 5 | 'sources': [ 6 | 'src/rapid-client.cc', 7 | ], 8 | 'dependencies': [ 9 | "&2 16 | exit 1 17 | fi 18 | 19 | cd deps 20 | . ./versions 21 | 22 | # unpack dependencies 23 | tar xzf ./curl-$CURL_VERSION.tar.gz --no-same-owner && \ 24 | tar xzf ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz --no-same-owner 25 | 26 | ( 27 | # Build Curl 28 | cd curl-curl-$CURL_VERSION && \ 29 | ./buildconf && \ 30 | ./configure \ 31 | --prefix "$ARTIFACTS_DIR" \ 32 | --disable-alt-svc \ 33 | --disable-ares \ 34 | --disable-cookies \ 35 | --disable-crypto-auth \ 36 | --disable-dateparse \ 37 | --disable-dict \ 38 | --disable-dnsshuffle \ 39 | --disable-doh \ 40 | --disable-file \ 41 | --disable-ftp \ 42 | --disable-get-easy-options \ 43 | --disable-gopher \ 44 | --disable-hsts \ 45 | --disable-http-auth \ 46 | --disable-imap \ 47 | --disable-ipv6 \ 48 | --disable-ldap \ 49 | --disable-ldaps \ 50 | --disable-libcurl-option \ 51 | --disable-manual \ 52 | --disable-mime \ 53 | --disable-mqtt \ 54 | --disable-netrc \ 55 | --disable-ntlm-wb \ 56 | --disable-pop3 \ 57 | --disable-progress-meter \ 58 | --disable-proxy \ 59 | --disable-pthreads \ 60 | --disable-rtsp \ 61 | --disable-shared \ 62 | --disable-smtp \ 63 | --disable-socketpair \ 64 | --disable-sspi \ 65 | --disable-telnet \ 66 | --disable-tftp \ 67 | --disable-threaded-resolver \ 68 | --disable-unix-sockets \ 69 | --disable-verbose \ 70 | --disable-versioned-symbols \ 71 | --with-pic \ 72 | --without-brotli \ 73 | --without-ca-bundle \ 74 | --without-gssapi \ 75 | --without-libidn2 \ 76 | --without-libpsl \ 77 | --without-librtmp \ 78 | --without-libssh2 \ 79 | --without-nghttp2 \ 80 | --without-nghttp3 \ 81 | --without-ngtcp2 \ 82 | --without-ssl \ 83 | --without-zlib \ 84 | --without-zstd && \ 85 | make && \ 86 | make install 87 | ) 88 | 89 | ( 90 | # Build aws-lambda-cpp 91 | mkdir -p ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE/build && \ 92 | cd ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE/build 93 | 94 | $CMAKE .. \ 95 | -DCMAKE_CXX_FLAGS="-fPIC" \ 96 | -DCMAKE_INSTALL_PREFIX="$ARTIFACTS_DIR" \ 97 | -DCMAKE_MODULE_PATH="$ARTIFACTS_DIR"/lib/pkgconfig && \ 98 | make && make install 99 | ) 100 | fi 101 | -------------------------------------------------------------------------------- /scripts/update_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | set -e 4 | 5 | cd deps 6 | 7 | source versions 8 | 9 | # clean up old files 10 | rm -f aws-lambda-cpp-*.tar.gz && rm -f curl-*.tar.gz 11 | 12 | # Grab Curl 13 | wget -c https://github.com/curl/curl/archive/refs/tags/curl-$CURL_VERSION.tar.gz -O - | tar -xz 14 | 15 | # Apply patches 16 | ( 17 | cd curl-curl-$CURL_VERSION && \ 18 | patch -p1 < ../patches/0001-curl-disable_wakeup.patch 19 | patch -p1 < ../patches/libcurl-configure-template.patch 20 | ) 21 | 22 | # Pack again and remove the folder 23 | tar -czvf curl-$CURL_VERSION.tar.gz curl-curl-$CURL_VERSION && \ 24 | rm -rf curl-curl-$CURL_VERSION 25 | 26 | # Grab aws-lambda-cpp 27 | wget -c https://github.com/awslabs/aws-lambda-cpp/archive/refs/tags/v$AWS_LAMBDA_CPP_RELEASE.tar.gz -O - | tar -xz 28 | 29 | # Apply patches 30 | ( 31 | cd aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE && \ 32 | patch -p1 < ../patches/aws-lambda-cpp-add-xray-response.patch && \ 33 | patch -p1 < ../patches/aws-lambda-cpp-add-tenant-id.patch 34 | ) 35 | 36 | # Pack again and remove the folder 37 | tar -czvf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE --no-same-owner && \ 38 | rm -rf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE 39 | -------------------------------------------------------------------------------- /src/BeforeExitListener.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * The runtime has a single beforeExit function which is stored in the global 5 | * object with a symbol key. 6 | * The symbol is not exported. 7 | * The process.beforeExit listener is setup in index.mjs along with all other 8 | * top-level process event listeners. 9 | */ 10 | 11 | 'use strict'; 12 | 13 | // define a named symbol for the handler function 14 | const LISTENER_SYMBOL = Symbol.for('aws.lambda.beforeExit'); 15 | const NO_OP_LISTENER = () => {}; 16 | 17 | // export a setter 18 | module.exports = { 19 | /** 20 | * Call the listener function with no arguments. 21 | */ 22 | invoke: () => global[LISTENER_SYMBOL](), 23 | 24 | /** 25 | * Reset the listener to a no-op function. 26 | */ 27 | reset: () => (global[LISTENER_SYMBOL] = NO_OP_LISTENER), 28 | 29 | /** 30 | * Set the listener to the provided function. 31 | */ 32 | set: (listener) => (global[LISTENER_SYMBOL] = listener), 33 | }; 34 | -------------------------------------------------------------------------------- /src/CallbackContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const BeforeExitListener = require('./BeforeExitListener.js'); 8 | const { structuredConsole } = require('./LogPatch'); 9 | 10 | /** 11 | * Build the callback function and the part of the context which exposes 12 | * the succeed/fail/done callbacks. 13 | * @param client {RuntimeClient} 14 | * The RAPID client used to post results/errors. 15 | * @param id {string} 16 | * The invokeId for the current invocation. 17 | * @param scheduleNext {function} 18 | * A function which takes no params and immediately schedules the next 19 | * iteration of the invoke loop. 20 | */ 21 | function _rawCallbackContext(client, id, scheduleNext) { 22 | const postError = (err, callback) => { 23 | structuredConsole.logError('Invoke Error', err); 24 | client.postInvocationError(err, id, callback); 25 | }; 26 | 27 | let isCompleteInvoked = false; 28 | const complete = (result, callback) => { 29 | if (isCompleteInvoked) { 30 | console.error( 31 | 'Invocation has already been reported as done. Cannot call complete more than once per invocation.', 32 | ); 33 | return; 34 | } 35 | isCompleteInvoked = true; 36 | client.postInvocationResponse(result, id, callback); 37 | }; 38 | 39 | let waitForEmptyEventLoop = true; 40 | 41 | const callback = function (err, result) { 42 | BeforeExitListener.reset(); 43 | if (err !== undefined && err !== null) { 44 | postError(err, scheduleNext); 45 | } else { 46 | if (!waitForEmptyEventLoop) { 47 | complete(result, scheduleNext); 48 | } else { 49 | BeforeExitListener.set(() => { 50 | setImmediate(() => { 51 | complete(result, scheduleNext); 52 | }); 53 | }); 54 | } 55 | } 56 | }; 57 | 58 | const done = (err, result) => { 59 | BeforeExitListener.reset(); 60 | if (err !== undefined && err !== null) { 61 | postError(err, scheduleNext); 62 | } else { 63 | complete(result, scheduleNext); 64 | } 65 | }; 66 | const succeed = (result) => { 67 | done(null, result); 68 | }; 69 | const fail = (err) => { 70 | if (err === undefined || err === null) { 71 | done('handled'); 72 | } else { 73 | done(err, null); 74 | } 75 | }; 76 | 77 | const callbackContext = { 78 | get callbackWaitsForEmptyEventLoop() { 79 | return waitForEmptyEventLoop; 80 | }, 81 | set callbackWaitsForEmptyEventLoop(value) { 82 | waitForEmptyEventLoop = value; 83 | }, 84 | succeed: succeed, 85 | fail: fail, 86 | done: done, 87 | }; 88 | 89 | return [ 90 | callback, 91 | callbackContext, 92 | function () { 93 | isCompleteInvoked = true; 94 | }, 95 | ]; 96 | } 97 | 98 | /** 99 | * Wraps the callback and context so that only the first call to any callback 100 | * succeeds. 101 | * @param callback {function} 102 | * the node-style callback function that was previously generated but not 103 | * yet wrapped. 104 | * @param callbackContext {object} 105 | * The previously generated callbackContext object that contains 106 | * getter/setters for the contextWaitsForEmptyeventLoop flag and the 107 | * succeed/fail/done functions. 108 | * @return [callback, context] 109 | */ 110 | function _wrappedCallbackContext(callback, callbackContext, markCompleted) { 111 | let finished = false; 112 | const onlyAllowFirstCall = function (toWrap) { 113 | return function () { 114 | if (!finished) { 115 | toWrap.apply(null, arguments); 116 | finished = true; 117 | } 118 | }; 119 | }; 120 | 121 | callbackContext.succeed = onlyAllowFirstCall(callbackContext.succeed); 122 | callbackContext.fail = onlyAllowFirstCall(callbackContext.fail); 123 | callbackContext.done = onlyAllowFirstCall(callbackContext.done); 124 | 125 | return [onlyAllowFirstCall(callback), callbackContext, markCompleted]; 126 | } 127 | 128 | /** 129 | * Construct the base-context object which includes the required flags and 130 | * callback methods for the Node programming model. 131 | * @param client {RAPIDClient} 132 | * The RAPID client used to post results/errors. 133 | * @param id {string} 134 | * The invokeId for the current invocation. 135 | * @param scheduleNext {function} 136 | * A function which takes no params and immediately schedules the next 137 | * iteration of the invoke loop. 138 | * @return [callback, context] 139 | * The same function and context object, but wrapped such that only the 140 | * first call to any function will be successful. All subsequent calls are 141 | * a no-op. 142 | */ 143 | module.exports.build = function (client, id, scheduleNext) { 144 | let rawCallbackContext = _rawCallbackContext(client, id, scheduleNext); 145 | return _wrappedCallbackContext(...rawCallbackContext); 146 | }; 147 | -------------------------------------------------------------------------------- /src/Errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Defines custom error types throwable by the runtime. 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const util = require('util'); 10 | 11 | function _isError(obj) { 12 | return ( 13 | obj && 14 | obj.name && 15 | obj.message && 16 | obj.stack && 17 | typeof obj.name === 'string' && 18 | typeof obj.message === 'string' && 19 | typeof obj.stack === 'string' 20 | ); 21 | } 22 | 23 | function intoError(err) { 24 | if (err instanceof Error) { 25 | return err; 26 | } else { 27 | return new Error(err); 28 | } 29 | } 30 | 31 | module.exports.intoError = intoError; 32 | 33 | /** 34 | * Attempt to convert an object into a response object. 35 | * This method accounts for failures when serializing the error object. 36 | */ 37 | function toRapidResponse(error) { 38 | try { 39 | if (util.types.isNativeError(error) || _isError(error)) { 40 | return { 41 | errorType: error.name?.replace(/\x7F/g, '%7F'), 42 | errorMessage: error.message?.replace(/\x7F/g, '%7F'), 43 | trace: error.stack.replace(/\x7F/g, '%7F').split('\n'), 44 | }; 45 | } else { 46 | return { 47 | errorType: typeof error, 48 | errorMessage: error.toString(), 49 | trace: [], 50 | }; 51 | } 52 | } catch (_err) { 53 | return { 54 | errorType: 'handled', 55 | errorMessage: 56 | 'callback called with Error argument, but there was a problem while retrieving one or more of its message, name, and stack', 57 | }; 58 | } 59 | } 60 | 61 | module.exports.toRapidResponse = toRapidResponse; 62 | 63 | /** 64 | * Format an error with the expected properties. 65 | * For compatability, the error string always starts with a tab. 66 | */ 67 | module.exports.toFormatted = (error) => { 68 | try { 69 | return ( 70 | '\t' + JSON.stringify(error, (_k, v) => _withEnumerableProperties(v)) 71 | ); 72 | } catch (err) { 73 | return '\t' + JSON.stringify(toRapidResponse(error)); 74 | } 75 | }; 76 | 77 | /** 78 | * Error name, message, code, and stack are all members of the superclass, which 79 | * means they aren't enumerable and don't normally show up in JSON.stringify. 80 | * This method ensures those interesting properties are available along with any 81 | * user-provided enumerable properties. 82 | */ 83 | function _withEnumerableProperties(error) { 84 | if (error instanceof Error) { 85 | let ret = Object.assign( 86 | { 87 | errorType: error.name, 88 | errorMessage: error.message, 89 | code: error.code, 90 | }, 91 | error, 92 | ); 93 | if (typeof error.stack == 'string') { 94 | ret.stack = error.stack.split('\n'); 95 | } 96 | return ret; 97 | } else { 98 | return error; 99 | } 100 | } 101 | 102 | const errorClasses = [ 103 | class ImportModuleError extends Error {}, 104 | class HandlerNotFound extends Error {}, 105 | class MalformedHandlerName extends Error {}, 106 | class UserCodeSyntaxError extends Error {}, 107 | class MalformedStreamingHandler extends Error {}, 108 | class InvalidStreamingOperation extends Error {}, 109 | class UnhandledPromiseRejection extends Error { 110 | constructor(reason, promise) { 111 | super(reason); 112 | this.reason = reason; 113 | this.promise = promise; 114 | } 115 | }, 116 | ]; 117 | 118 | errorClasses.forEach((e) => { 119 | module.exports[e.name] = e; 120 | e.prototype.name = `Runtime.${e.name}`; 121 | }); 122 | -------------------------------------------------------------------------------- /src/HttpResponseStream.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * HttpResponseStream is NOT used by the runtime. 5 | * It is only exposed in the `awslambda` variable for customers to use. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const METADATA_PRELUDE_CONTENT_TYPE = 11 | 'application/vnd.awslambda.http-integration-response'; 12 | const DELIMITER_LEN = 8; 13 | 14 | // Implements the application/vnd.awslambda.http-integration-response content type. 15 | class HttpResponseStream { 16 | static from(underlyingStream, prelude) { 17 | underlyingStream.setContentType(METADATA_PRELUDE_CONTENT_TYPE); 18 | 19 | // JSON.stringify is required. NULL byte is not allowed in metadataPrelude. 20 | const metadataPrelude = JSON.stringify(prelude); 21 | 22 | underlyingStream._onBeforeFirstWrite = (write) => { 23 | write(metadataPrelude); 24 | 25 | // Write 8 null bytes after the JSON prelude. 26 | write(new Uint8Array(DELIMITER_LEN)); 27 | }; 28 | 29 | return underlyingStream; 30 | } 31 | } 32 | 33 | module.exports.HttpResponseStream = HttpResponseStream; 34 | -------------------------------------------------------------------------------- /src/InvokeContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * This module defines the InvokeContext and supporting functions. The 5 | * InvokeContext is responsible for pulling information from the invoke headers 6 | * and for wrapping the Rapid Client object's error and response functions. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | const assert = require('assert').strict; 12 | let { setCurrentRequestId, setCurrentTenantId } = require('./LogPatch'); 13 | 14 | const INVOKE_HEADER = { 15 | ClientContext: 'lambda-runtime-client-context', 16 | CognitoIdentity: 'lambda-runtime-cognito-identity', 17 | ARN: 'lambda-runtime-invoked-function-arn', 18 | AWSRequestId: 'lambda-runtime-aws-request-id', 19 | DeadlineMs: 'lambda-runtime-deadline-ms', 20 | XRayTrace: 'lambda-runtime-trace-id', 21 | TenantId: 'lambda-runtime-aws-tenant-id', 22 | }; 23 | 24 | module.exports = class InvokeContext { 25 | constructor(headers) { 26 | this.headers = _enforceLowercaseKeys(headers); 27 | } 28 | 29 | /** 30 | * The invokeId for this request. 31 | */ 32 | get invokeId() { 33 | let id = this.headers[INVOKE_HEADER.AWSRequestId]; 34 | assert.ok(id, 'invocation id is missing or invalid'); 35 | return id; 36 | } 37 | 38 | /** 39 | * The tenantId for this request. 40 | */ 41 | get tenantId() { 42 | return this.headers[INVOKE_HEADER.TenantId]; 43 | } 44 | 45 | /** 46 | * Push relevant invoke data into the logging context. 47 | */ 48 | updateLoggingContext() { 49 | setCurrentRequestId(this.invokeId); 50 | setCurrentTenantId(this.tenantId); 51 | } 52 | 53 | /** 54 | * Attach all of the relavant environmental and invocation data to the 55 | * provided object. 56 | * This method can throw if the headers are malformed and cannot be parsed. 57 | * @param callbackContext {Object} 58 | * The callbackContext object returned by a call to buildCallbackContext(). 59 | * @return {Object} 60 | * The user context object with all required data populated from the headers 61 | * and environment variables. 62 | */ 63 | attachEnvironmentData(callbackContext) { 64 | this._forwardXRay(); 65 | return Object.assign( 66 | callbackContext, 67 | this._environmentalData(), 68 | this._headerData(), 69 | ); 70 | } 71 | 72 | /** 73 | * All parts of the user-facing context object which are provided through 74 | * environment variables. 75 | */ 76 | _environmentalData() { 77 | return { 78 | functionVersion: process.env['AWS_LAMBDA_FUNCTION_VERSION'], 79 | functionName: process.env['AWS_LAMBDA_FUNCTION_NAME'], 80 | memoryLimitInMB: process.env['AWS_LAMBDA_FUNCTION_MEMORY_SIZE'], 81 | logGroupName: process.env['AWS_LAMBDA_LOG_GROUP_NAME'], 82 | logStreamName: process.env['AWS_LAMBDA_LOG_STREAM_NAME'], 83 | }; 84 | } 85 | 86 | /** 87 | * All parts of the user-facing context object which are provided through 88 | * request headers. 89 | */ 90 | _headerData() { 91 | const deadline = this.headers[INVOKE_HEADER.DeadlineMs]; 92 | return { 93 | clientContext: _parseJson( 94 | this.headers[INVOKE_HEADER.ClientContext], 95 | 'ClientContext', 96 | ), 97 | identity: _parseJson( 98 | this.headers[INVOKE_HEADER.CognitoIdentity], 99 | 'CognitoIdentity', 100 | ), 101 | invokedFunctionArn: this.headers[INVOKE_HEADER.ARN], 102 | awsRequestId: this.headers[INVOKE_HEADER.AWSRequestId], 103 | tenantId: this.headers[INVOKE_HEADER.TenantId], 104 | getRemainingTimeInMillis: function () { 105 | return deadline - Date.now(); 106 | }, 107 | }; 108 | } 109 | 110 | /** 111 | * Forward the XRay header into the environment variable. 112 | */ 113 | _forwardXRay() { 114 | if (this.headers[INVOKE_HEADER.XRayTrace]) { 115 | process.env['_X_AMZN_TRACE_ID'] = this.headers[INVOKE_HEADER.XRayTrace]; 116 | } else { 117 | delete process.env['_X_AMZN_TRACE_ID']; 118 | } 119 | } 120 | }; 121 | 122 | /** 123 | * Parse a JSON string and throw a readable error if something fails. 124 | * @param jsonString {string} - the string to attempt to parse 125 | * @param name {name} - the name to use when describing the string in an error 126 | * @return object - the parsed object 127 | * @throws if jsonString cannot be parsed 128 | */ 129 | function _parseJson(jsonString, name) { 130 | if (jsonString !== undefined) { 131 | try { 132 | return JSON.parse(jsonString); 133 | } catch (err) { 134 | throw new Error(`Cannot parse ${name} as json: ${err.toString()}`); 135 | } 136 | } else { 137 | return undefined; 138 | } 139 | } 140 | 141 | /** 142 | * Construct a copy of an object such that all of its keys are lowercase. 143 | */ 144 | function _enforceLowercaseKeys(original) { 145 | return Object.keys(original).reduce((enforced, originalKey) => { 146 | enforced[originalKey.toLowerCase()] = original[originalKey]; 147 | return enforced; 148 | }, {}); 149 | } 150 | -------------------------------------------------------------------------------- /src/NativeModuleLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | exports.load = () => require('./rapid-client.node'); 8 | -------------------------------------------------------------------------------- /src/RAPIDClient.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * This module defines the RAPID client which is responsible for all HTTP 5 | * interactions with the RAPID layer. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const Errors = require('./Errors'); 11 | const XRayError = require('./XRayError'); 12 | const ERROR_TYPE_HEADER = 'Lambda-Runtime-Function-Error-Type'; 13 | const { createResponseStream } = require('./ResponseStream'); 14 | 15 | /** 16 | * Objects of this class are responsible for all interactions with the RAPID 17 | * API. 18 | */ 19 | module.exports = class RAPIDClient { 20 | constructor(hostnamePort, httpClient, nativeClient) { 21 | this.http = httpClient || require('http'); 22 | this.nativeClient = 23 | nativeClient || require('./NativeModuleLoader.js').load(); 24 | this.useAlternativeClient = 25 | process.env['AWS_LAMBDA_NODEJS_USE_ALTERNATIVE_CLIENT_1'] === 'true'; 26 | 27 | let [hostname, port] = hostnamePort.split(':'); 28 | this.hostname = hostname; 29 | this.port = parseInt(port, 10); 30 | this.agent = new this.http.Agent({ 31 | keepAlive: true, 32 | maxSockets: 1, 33 | }); 34 | } 35 | 36 | /** 37 | * Complete and invocation with the provided response. 38 | * @param {Object} response 39 | * An arbitrary object to convert to JSON and send back as as response. 40 | * @param {String} id 41 | * The invocation ID. 42 | * @param {Function} callback 43 | * The callback to run after the POST response ends 44 | */ 45 | postInvocationResponse(response, id, callback) { 46 | let bodyString = _trySerializeResponse(response); 47 | this.nativeClient.done(encodeURIComponent(id), bodyString); 48 | callback(); 49 | } 50 | 51 | /** 52 | * Stream the invocation response. 53 | * @param {String} id 54 | * The invocation ID. 55 | * @param {Function} callback 56 | * The callback to run after the POST response ends 57 | * @return {object} 58 | * A response stream and a Promise that resolves when the stream is done. 59 | */ 60 | getStreamForInvocationResponse(id, callback, options) { 61 | const ret = createResponseStream({ 62 | httpOptions: { 63 | agent: this.agent, 64 | http: this.http, 65 | hostname: this.hostname, 66 | method: 'POST', 67 | port: this.port, 68 | path: 69 | '/2018-06-01/runtime/invocation/' + 70 | encodeURIComponent(id) + 71 | '/response', 72 | highWaterMark: options?.highWaterMark, 73 | }, 74 | }); 75 | 76 | return { 77 | request: ret.request, 78 | responseDone: ret.responseDone.then((_) => { 79 | if (callback) { 80 | callback(); 81 | } 82 | }), 83 | }; 84 | } 85 | 86 | /** 87 | * Post an initialization error to the RAPID API. 88 | * @param {Error} error 89 | * @param {Function} callback 90 | * The callback to run after the POST response ends 91 | */ 92 | postInitError(error, callback) { 93 | let response = Errors.toRapidResponse(error); 94 | this._post( 95 | `/2018-06-01/runtime/init/error`, 96 | response, 97 | { [ERROR_TYPE_HEADER]: response.errorType }, 98 | callback, 99 | ); 100 | } 101 | 102 | /** 103 | * Post an invocation error to the RAPID API 104 | * @param {Error} error 105 | * @param {String} id 106 | * The invocation ID for the in-progress invocation. 107 | * @param {Function} callback 108 | * The callback to run after the POST response ends 109 | */ 110 | postInvocationError(error, id, callback) { 111 | let response = Errors.toRapidResponse(error); 112 | let bodyString = _trySerializeResponse(response); 113 | let xrayString = XRayError.formatted(error); 114 | this.nativeClient.error(encodeURIComponent(id), bodyString, xrayString); 115 | callback(); 116 | } 117 | 118 | /** 119 | * Get the next invocation. 120 | * @return {PromiseLike.} 121 | * A promise which resolves to an invocation object that contains the body 122 | * as json and the header array. e.g. {bodyJson, headers} 123 | */ 124 | async nextInvocation() { 125 | if (this.useAlternativeClient) { 126 | const options = { 127 | hostname: this.hostname, 128 | port: this.port, 129 | path: '/2018-06-01/runtime/invocation/next', 130 | method: 'GET', 131 | agent: this.agent, 132 | }; 133 | return new Promise((resolve, reject) => { 134 | let request = this.http.request(options, (response) => { 135 | let data = ''; 136 | response 137 | .setEncoding('utf-8') 138 | .on('data', (chunk) => { 139 | data += chunk; 140 | }) 141 | .on('end', () => { 142 | resolve({ 143 | bodyJson: data, 144 | headers: response.headers, 145 | }); 146 | }); 147 | }); 148 | request 149 | .on('error', (e) => { 150 | reject(e); 151 | }) 152 | .end(); 153 | }); 154 | } 155 | 156 | return this.nativeClient.next(); 157 | } 158 | 159 | /** 160 | * HTTP Post to a path. 161 | * @param {String} path 162 | * @param {Object} body 163 | * The body is serialized into JSON before posting. 164 | * @param {Object} headers 165 | * The http headers 166 | * @param (function()} callback 167 | * The callback to run after the POST response ends 168 | */ 169 | _post(path, body, headers, callback) { 170 | let bodyString = _trySerializeResponse(body); 171 | const options = { 172 | hostname: this.hostname, 173 | port: this.port, 174 | path: path, 175 | method: 'POST', 176 | headers: Object.assign( 177 | { 178 | 'Content-Type': 'application/json', 179 | 'Content-Length': Buffer.from(bodyString).length, 180 | }, 181 | headers || {}, 182 | ), 183 | agent: this.agent, 184 | }; 185 | let request = this.http.request(options, (response) => { 186 | response 187 | .on('end', () => { 188 | callback(); 189 | }) 190 | .on('error', (e) => { 191 | throw e; 192 | }) 193 | .on('data', () => {}); 194 | }); 195 | request 196 | .on('error', (e) => { 197 | throw e; 198 | }) 199 | .end(bodyString, 'utf-8'); 200 | } 201 | }; 202 | 203 | /** 204 | * Attempt to serialize an object as json. Capture the failure if it occurs and 205 | * throw one that's known to be serializable. 206 | */ 207 | function _trySerializeResponse(body) { 208 | try { 209 | return JSON.stringify(body === undefined ? null : body); 210 | } catch (err) { 211 | throw new Error('Unable to stringify response body'); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/Runtime.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * This module defines the top-level Runtime class which controls the 5 | * bootstrap's execution flow. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const InvokeContext = require('./InvokeContext.js'); 11 | const CallbackContext = require('./CallbackContext.js'); 12 | const StreamingContext = require('./StreamingContext.js'); 13 | const BeforeExitListener = require('./BeforeExitListener.js'); 14 | const { STREAM_RESPONSE } = require('./UserFunction.js'); 15 | const { verbose, vverbose } = require('./VerboseLog.js').logger('RAPID'); 16 | 17 | module.exports = class Runtime { 18 | constructor(client, handler, handlerMetadata, errorCallbacks) { 19 | this.client = client; 20 | this.handler = handler; 21 | this.handlerMetadata = handlerMetadata; 22 | this.errorCallbacks = errorCallbacks; 23 | this.handleOnce = 24 | handlerMetadata.streaming === STREAM_RESPONSE 25 | ? this.handleOnceStreaming 26 | : this.handleOnceNonStreaming; 27 | } 28 | 29 | /** 30 | * Schedule the next loop iteration to start at the beginning of the next time 31 | * around the event loop. 32 | */ 33 | scheduleIteration() { 34 | let that = this; 35 | setImmediate(() => { 36 | that.handleOnce().then( 37 | // Success is a no-op at this level. There are 2 cases: 38 | // 1 - The user used one of the callback functions which already 39 | // schedules the next iteration. 40 | // 2 - The next iteration is not scheduled because the 41 | // waitForEmptyEventLoop was set. In this case the beforeExit 42 | // handler will automatically start the next iteration. 43 | () => {}, 44 | 45 | // Errors should not reach this level in typical execution. If they do 46 | // it's a sign of an issue in the Client or a bug in the runtime. So 47 | // dump it to the console and attempt to report it as a Runtime error. 48 | (err) => { 49 | console.log(`Unexpected Top Level Error: ${err.toString()}`); 50 | this.errorCallbacks.uncaughtException(err); 51 | }, 52 | ); 53 | }); 54 | } 55 | 56 | /** 57 | * Wait for the next invocation, process it, and schedule the next iteration. 58 | */ 59 | async handleOnceNonStreaming() { 60 | let { bodyJson, headers } = await this.client.nextInvocation(); 61 | let invokeContext = new InvokeContext(headers); 62 | invokeContext.updateLoggingContext(); 63 | 64 | let [callback, callbackContext, markCompleted] = CallbackContext.build( 65 | this.client, 66 | invokeContext.invokeId, 67 | this.scheduleIteration.bind(this), 68 | ); 69 | 70 | try { 71 | this._setErrorCallbacks(invokeContext.invokeId); 72 | this._setDefaultExitListener(invokeContext.invokeId, markCompleted); 73 | 74 | let result = this.handler( 75 | JSON.parse(bodyJson), 76 | invokeContext.attachEnvironmentData(callbackContext), 77 | callback, 78 | ); 79 | 80 | if (_isPromise(result)) { 81 | result 82 | .then(callbackContext.succeed, callbackContext.fail) 83 | .catch(callbackContext.fail); 84 | } 85 | } catch (err) { 86 | callback(err); 87 | } 88 | } 89 | 90 | /** 91 | * Wait for the next invocation, process it, and schedule the next iteration. 92 | */ 93 | async handleOnceStreaming() { 94 | let { bodyJson, headers } = await this.client.nextInvocation(); 95 | 96 | let invokeContext = new InvokeContext(headers); 97 | invokeContext.updateLoggingContext(); 98 | 99 | let streamingContext = StreamingContext.build( 100 | this.client, 101 | invokeContext.invokeId, 102 | this.scheduleIteration.bind(this), 103 | this.handlerMetadata?.highWaterMark 104 | ? { highWaterMark: this.handlerMetadata.highWaterMark } 105 | : undefined, 106 | ); 107 | 108 | const { 109 | responseStream, 110 | rapidResponse, 111 | scheduleNext, 112 | fail: ctxFail, 113 | } = streamingContext.createStream(); 114 | delete streamingContext.createStream; 115 | 116 | try { 117 | this._setErrorCallbacks(invokeContext.invokeId); 118 | this._setStreamingExitListener(invokeContext.invokeId, responseStream); 119 | 120 | const ctx = invokeContext.attachEnvironmentData(streamingContext); 121 | 122 | verbose('Runtime::handleOnceStreaming', 'invoking handler'); 123 | const event = JSON.parse(bodyJson); 124 | const handlerResult = this.handler(event, responseStream, ctx); 125 | verbose('Runtime::handleOnceStreaming', 'handler returned'); 126 | 127 | if (!_isPromise(handlerResult)) { 128 | verbose('Runtime got non-promise response'); 129 | ctxFail('Streaming does not support non-async handlers.', scheduleNext); 130 | 131 | return; 132 | } 133 | 134 | const result = await handlerResult; 135 | if (typeof result !== 'undefined') { 136 | console.warn('Streaming handlers ignore return values.'); 137 | } 138 | verbose('Runtime::handleOnceStreaming result is awaited.'); 139 | 140 | // await for the rapid response if present. 141 | if (rapidResponse) { 142 | const res = await rapidResponse; 143 | vverbose('RAPID response', res); 144 | } 145 | 146 | if (!responseStream.writableFinished) { 147 | ctxFail('Response stream is not finished.', scheduleNext); 148 | return; 149 | } 150 | 151 | // Handler returned and response has ended. 152 | scheduleNext(); 153 | } catch (err) { 154 | verbose('Runtime::handleOnceStreaming::finally stream destroyed'); 155 | ctxFail(err, scheduleNext); 156 | } 157 | } 158 | 159 | /** 160 | * Replace the error handler callbacks. 161 | * @param {String} invokeId 162 | */ 163 | _setErrorCallbacks(invokeId) { 164 | this.errorCallbacks.uncaughtException = (error) => { 165 | this.client.postInvocationError(error, invokeId, () => { 166 | process.exit(129); 167 | }); 168 | }; 169 | this.errorCallbacks.unhandledRejection = (error) => { 170 | this.client.postInvocationError(error, invokeId, () => { 171 | process.exit(128); 172 | }); 173 | }; 174 | } 175 | 176 | /** 177 | * Setup the 'beforeExit' listener that is used if the callback is never 178 | * called and the handler is not async. 179 | * CallbackContext replaces the listener if a callback is invoked. 180 | */ 181 | _setDefaultExitListener(invokeId, markCompleted) { 182 | BeforeExitListener.set(() => { 183 | markCompleted(); 184 | this.client.postInvocationResponse(null, invokeId, () => 185 | this.scheduleIteration(), 186 | ); 187 | }); 188 | } 189 | 190 | /** 191 | * Setup the 'beforeExit' listener that is used if the callback is never 192 | * called and the handler is not async. 193 | * CallbackContext replaces the listener if a callback is invoked. 194 | */ 195 | _setStreamingExitListener(_invokeId) { 196 | BeforeExitListener.set(() => { 197 | this.scheduleIteration(); 198 | }); 199 | } 200 | }; 201 | 202 | function _isPromise(obj) { 203 | return obj && obj.then && typeof obj.then === 'function'; 204 | } 205 | -------------------------------------------------------------------------------- /src/StreamingContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const BeforeExitListener = require('./BeforeExitListener.js'); 8 | const { InvalidStreamingOperation } = require('./Errors'); 9 | const { verbose, vverbose } = require('./VerboseLog.js').logger('STREAM'); 10 | const { tryCallFail } = require('./ResponseStream'); 11 | const { structuredConsole } = require('./LogPatch'); 12 | 13 | /** 14 | * Construct the base-context object which includes the required flags and 15 | * callback methods for the Node programming model. 16 | * @param client {RAPIDClient} 17 | * The RAPID client used to post results/errors. 18 | * @param id {string} 19 | * The invokeId for the current invocation. 20 | * @param scheduleNext {function} 21 | * A function which takes no params and immediately schedules the next 22 | * iteration of the invoke loop. 23 | * @param options {object} 24 | * An object with optional properties for streaming. 25 | * @return {context} 26 | * Context object that has the createStream function. 27 | */ 28 | module.exports.build = function (client, id, scheduleNext, options) { 29 | let waitForEmptyEventLoop = true; 30 | 31 | const scheduleNextNow = () => { 32 | verbose('StreamingContext::scheduleNextNow entered'); 33 | if (!waitForEmptyEventLoop) { 34 | scheduleNext(); 35 | } else { 36 | BeforeExitListener.set(() => { 37 | setImmediate(() => { 38 | scheduleNext(); 39 | }); 40 | }); 41 | } 42 | }; 43 | 44 | let isStreamCreated = false; 45 | const streamingContext = { 46 | get callbackWaitsForEmptyEventLoop() { 47 | return waitForEmptyEventLoop; 48 | }, 49 | set callbackWaitsForEmptyEventLoop(value) { 50 | waitForEmptyEventLoop = value; 51 | }, 52 | createStream: (callback) => { 53 | if (isStreamCreated) { 54 | throw new InvalidStreamingOperation( 55 | 'Cannot create stream for the same StreamingContext more than once.', 56 | ); 57 | } 58 | 59 | const { request: responseStream, responseDone: rapidResponse } = 60 | client.getStreamForInvocationResponse(id, callback, options); 61 | 62 | isStreamCreated = true; 63 | vverbose('StreamingContext::createStream stream created'); 64 | 65 | return { 66 | fail: (err, callback) => { 67 | structuredConsole.logError('Invoke Error', err); 68 | 69 | tryCallFail(responseStream, err, callback); 70 | }, 71 | responseStream, 72 | rapidResponse, 73 | scheduleNext: () => { 74 | verbose('StreamingContext::createStream scheduleNext'); 75 | BeforeExitListener.reset(); 76 | scheduleNextNow(); 77 | }, 78 | }; 79 | }, 80 | }; 81 | 82 | return streamingContext; 83 | }; 84 | -------------------------------------------------------------------------------- /src/VerboseLog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const EnvVarName = 'AWS_LAMBDA_RUNTIME_VERBOSE'; 8 | const Tag = 'RUNTIME'; 9 | const Verbosity = (() => { 10 | if (!process.env[EnvVarName]) { 11 | return 0; 12 | } 13 | 14 | try { 15 | const verbosity = parseInt(process.env[EnvVarName]); 16 | return verbosity < 0 ? 0 : verbosity > 3 ? 3 : verbosity; 17 | } catch (_) { 18 | return 0; 19 | } 20 | })(); 21 | 22 | exports.logger = function (category) { 23 | return { 24 | verbose: function () { 25 | if (Verbosity >= 1) { 26 | const args = [...arguments].map((arg) => 27 | typeof arg === 'function' ? arg() : arg, 28 | ); 29 | console.log.apply(null, [Tag, category, ...args]); 30 | } 31 | }, 32 | vverbose: function () { 33 | if (Verbosity >= 2) { 34 | const args = [...arguments].map((arg) => 35 | typeof arg === 'function' ? arg() : arg, 36 | ); 37 | console.log.apply(null, [Tag, category, ...args]); 38 | } 39 | }, 40 | vvverbose: function () { 41 | if (Verbosity >= 3) { 42 | const args = [...arguments].map((arg) => 43 | typeof arg === 'function' ? arg() : arg, 44 | ); 45 | console.log.apply(null, [Tag, category, ...args]); 46 | } 47 | }, 48 | }; 49 | }; 50 | -------------------------------------------------------------------------------- /src/XRayError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | module.exports.formatted = (err) => { 8 | try { 9 | return JSON.stringify(new XRayFormattedCause(err)); 10 | } catch (err) { 11 | return ''; 12 | } 13 | }; 14 | 15 | /** 16 | * prepare an exception blob for sending to AWS X-Ray 17 | * adapted from https://code.amazon.com/packages/AWSTracingSDKNode/blobs/c917508ca4fce6a795f95dc30c91b70c6bc6c617/--/core/lib/segments/attributes/captured_exception.js 18 | * transform an Error, or Error-like, into an exception parseable by X-Ray's service. 19 | * { 20 | * "name": "CustomException", 21 | * "message": "Something bad happend!", 22 | * "stack": [ 23 | * "exports.handler (/var/task/node_modules/event_invoke.js:3:502) 24 | * ] 25 | * } 26 | * => 27 | * { 28 | * "working_directory": "/var/task", 29 | * "exceptions": [ 30 | * { 31 | * "type": "CustomException", 32 | * "message": "Something bad happend!", 33 | * "stack": [ 34 | * { 35 | * "path": "/var/task/event_invoke.js", 36 | * "line": 502, 37 | * "label": "exports.throw_custom_exception" 38 | * } 39 | * ] 40 | * } 41 | * ], 42 | * "paths": [ 43 | * "/var/task/event_invoke.js" 44 | * ] 45 | * } 46 | */ 47 | class XRayFormattedCause { 48 | constructor(err) { 49 | this.working_directory = process.cwd(); // eslint-disable-line 50 | 51 | let stack = []; 52 | if (err.stack) { 53 | let stackLines = err.stack.replace(/\x7F/g, '%7F').split('\n'); 54 | stackLines.shift(); 55 | 56 | stackLines.forEach((stackLine) => { 57 | let line = stackLine.trim().replace(/\(|\)/g, ''); 58 | line = line.substring(line.indexOf(' ') + 1); 59 | 60 | let label = 61 | line.lastIndexOf(' ') >= 0 62 | ? line.slice(0, line.lastIndexOf(' ')) 63 | : null; 64 | let path = 65 | label == undefined || label == null || label.length === 0 66 | ? line 67 | : line.slice(line.lastIndexOf(' ') + 1); 68 | path = path.split(':'); 69 | 70 | let entry = { 71 | path: path[0], 72 | line: parseInt(path[1]), 73 | label: label || 'anonymous', 74 | }; 75 | 76 | stack.push(entry); 77 | }); 78 | } 79 | 80 | this.exceptions = [ 81 | { 82 | type: err.name?.replace(/\x7F/g, '%7F'), 83 | message: err.message?.replace(/\x7F/g, '%7F'), 84 | stack: stack, 85 | }, 86 | ]; 87 | 88 | let paths = new Set(); 89 | stack.forEach((entry) => { 90 | paths.add(entry.path); 91 | }); 92 | this.paths = Array.from(paths); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/build.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const { build } = require('esbuild'); 8 | const fs = require('fs'); 9 | 10 | const shared = { 11 | bundle: true, 12 | entryPoints: ['index.mjs'], 13 | external: ['./rapid-client.node'], 14 | logLevel: 'info', 15 | minify: false, 16 | platform: 'node', 17 | format: 'esm', 18 | charset: 'utf8', 19 | banner: { 20 | js: `/** Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. */\nimport { createRequire } from \"module\";\nconst require = createRequire(import.meta.url);`, 21 | }, 22 | }; 23 | 24 | const buildOneSet = (target) => { 25 | build({ 26 | ...shared, 27 | outfile: `../dist/index.mjs`, 28 | target, 29 | }); 30 | 31 | // Keep backward compatibility for Node14 32 | if (process.version.startsWith('v14')) { 33 | build({ 34 | ...shared, 35 | format: 'cjs', 36 | entryPoints: ['UserFunction.js'], 37 | banner: { 38 | js: '(function (){', 39 | }, 40 | footer: { 41 | js: '})();', 42 | }, 43 | outfile: `../dist/UserFunction.js`, 44 | target, 45 | }); 46 | } 47 | }; 48 | 49 | buildOneSet('node14.21.3'); 50 | -------------------------------------------------------------------------------- /src/index.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * This module is the bootstrap entrypoint. It establishes the top-level event 5 | * listeners and loads the user's code. 6 | */ 7 | 8 | const RAPIDClient = require('./RAPIDClient.js'); 9 | const Runtime = require('./Runtime.js'); 10 | const UserFunction = require('./UserFunction.js'); 11 | const Errors = require('./Errors.js'); 12 | const BeforeExitListener = require('./BeforeExitListener.js'); 13 | const LogPatch = require('./LogPatch'); 14 | 15 | export async function run(appRootOrHandler, handler = '') { 16 | LogPatch.patchConsole(); 17 | const client = new RAPIDClient(process.env.AWS_LAMBDA_RUNTIME_API); 18 | 19 | let errorCallbacks = { 20 | uncaughtException: (error) => { 21 | client.postInitError(error, () => process.exit(129)); 22 | }, 23 | unhandledRejection: (error) => { 24 | client.postInitError(error, () => process.exit(128)); 25 | }, 26 | }; 27 | 28 | process.on('uncaughtException', (error) => { 29 | LogPatch.structuredConsole.logError('Uncaught Exception', error); 30 | errorCallbacks.uncaughtException(error); 31 | }); 32 | 33 | process.on('unhandledRejection', (reason, promise) => { 34 | let error = new Errors.UnhandledPromiseRejection(reason, promise); 35 | LogPatch.structuredConsole.logError('Unhandled Promise Rejection', error); 36 | errorCallbacks.unhandledRejection(error); 37 | }); 38 | 39 | BeforeExitListener.reset(); 40 | process.on('beforeExit', BeforeExitListener.invoke); 41 | 42 | const handlerFunc = UserFunction.isHandlerFunction(appRootOrHandler) 43 | ? appRootOrHandler 44 | : await UserFunction.load(appRootOrHandler, handler); 45 | 46 | const metadata = UserFunction.getHandlerMetadata(handlerFunc); 47 | new Runtime( 48 | client, 49 | handlerFunc, 50 | metadata, 51 | errorCallbacks, 52 | ).scheduleIteration(); 53 | } 54 | -------------------------------------------------------------------------------- /src/rapid-client.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-present Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). 5 | * You may not use this file except in compliance with the License. 6 | * A copy of the License is located at 7 | * 8 | * http://aws.amazon.com/apache2.0 9 | * 10 | * or in the "license" file accompanying this file. This file is distributed 11 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 | * express or implied. See the License for the specific language governing 13 | * permissions and limitations under the License. 14 | */ 15 | 16 | #include 17 | #include "aws/lambda-runtime/runtime.h" 18 | #include "napi.h" 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | static const std::string ENDPOINT(getenv("AWS_LAMBDA_RUNTIME_API")); 25 | static aws::lambda_runtime::runtime CLIENT(ENDPOINT); 26 | 27 | // Napi::AsyncWorker docs: https://github.com/nodejs/node-addon-api/blob/main/doc/async_worker.md 28 | class RuntimeApiNextPromiseWorker : public Napi::AsyncWorker { 29 | public: 30 | RuntimeApiNextPromiseWorker(Napi::Env &env) 31 | : Napi::AsyncWorker(env), response(), deferred(Napi::Promise::Deferred::New(env)) {} 32 | 33 | ~RuntimeApiNextPromiseWorker() {} 34 | 35 | void Execute() override { 36 | auto outcome = CLIENT.get_next(); 37 | if (!outcome.is_success()) { 38 | std::string err = "Failed to get next invocation, error "; 39 | err.append(std::to_string(static_cast(outcome.get_failure()))); 40 | SetError(err); 41 | return; 42 | } 43 | 44 | // TODO: See if json parsing works on V8:Buffer objects, which might be a way to reduce copying large payloads 45 | response = outcome.get_result(); 46 | } 47 | 48 | Napi::Value Promise() { 49 | return deferred.Promise(); 50 | } 51 | 52 | void OnOK() override { 53 | Napi::Env env = Env(); 54 | auto response_data = Napi::String::New(env, response.payload.c_str()); 55 | 56 | // TODO: The current javascript code (InvokeContext.js) to handle the header values itself. 57 | // These type conversions might be replaced by returning the final context object. 58 | auto headers = Napi::Object::New(env); 59 | headers.Set( 60 | Napi::String::New(env, "lambda-runtime-deadline-ms"), 61 | Napi::Number::New(env, 62 | std::chrono::duration_cast( 63 | response.deadline.time_since_epoch() 64 | ).count() 65 | )); 66 | headers.Set( 67 | Napi::String::New(env, "lambda-runtime-aws-request-id"), 68 | Napi::String::New(env, response.request_id.c_str())); 69 | headers.Set( 70 | Napi::String::New(env, "lambda-runtime-trace-id"), 71 | Napi::String::New(env, response.xray_trace_id.c_str())); 72 | headers.Set( 73 | Napi::String::New(env, "lambda-runtime-invoked-function-arn"), 74 | Napi::String::New(env, response.function_arn.c_str())); 75 | if (response.client_context != "") { 76 | headers.Set( 77 | Napi::String::New(env, "lambda-runtime-client-context"), 78 | Napi::String::New(env, response.client_context.c_str())); 79 | } 80 | if (response.cognito_identity != "") { 81 | headers.Set( 82 | Napi::String::New(env, "lambda-runtime-cognito-identity"), 83 | Napi::String::New(env, response.cognito_identity.c_str())); 84 | } 85 | if (response.tenant_id != "") { 86 | headers.Set( 87 | Napi::String::New(env, "lambda-runtime-aws-tenant-id"), 88 | Napi::String::New(env, response.tenant_id.c_str())); 89 | } 90 | 91 | auto ret = Napi::Object::New(env); 92 | ret.Set(Napi::String::New(env, "bodyJson"), response_data); 93 | ret.Set(Napi::String::New(env, "headers"), headers); 94 | 95 | deferred.Resolve(ret); 96 | } 97 | 98 | void OnError(Napi::Error const &error) override { 99 | deferred.Reject(error.Value()); 100 | } 101 | 102 | private: 103 | aws::lambda_runtime::invocation_request response; 104 | Napi::Promise::Deferred deferred; 105 | }; 106 | 107 | 108 | Napi::Value Next(const Napi::CallbackInfo & info) 109 | { 110 | Napi::Env env = info.Env(); 111 | RuntimeApiNextPromiseWorker *nextWorker = new RuntimeApiNextPromiseWorker(env); 112 | nextWorker->Queue(); 113 | return nextWorker->Promise(); 114 | } 115 | 116 | Napi::Value Done(const Napi::CallbackInfo & info) 117 | { 118 | Napi::Env env = info.Env(); 119 | if (info.Length() < 2) { 120 | Napi::TypeError::New(env, "Wrong number of arguments, expected 2").ThrowAsJavaScriptException(); 121 | return env.Null(); 122 | } 123 | if (!info[0].IsString() || !info[1].IsString()) { 124 | Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException(); 125 | return env.Null(); 126 | } 127 | auto requestId = info[0].As(); 128 | auto responseString = info[1].As(); 129 | auto response = aws::lambda_runtime::invocation_response::success(responseString.Utf8Value(), "application/json"); 130 | auto outcome = CLIENT.post_success(requestId.Utf8Value(), response); 131 | return env.Null(); 132 | } 133 | 134 | Napi::Value Error(const Napi::CallbackInfo & info) 135 | { 136 | Napi::Env env = info.Env(); 137 | if (info.Length() < 3) { 138 | Napi::TypeError::New(env, "Wrong number of arguments, expected 3").ThrowAsJavaScriptException(); 139 | return env.Null(); 140 | } 141 | if (!info[0].IsString() || !info[1].IsString() || !info[2].IsString()) { 142 | Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException(); 143 | return env.Null(); 144 | } 145 | auto requestId = info[0].As(); 146 | auto responseString = info[1].As(); 147 | auto xrayResponse = info[2].As(); 148 | auto response = aws::lambda_runtime::invocation_response(responseString.Utf8Value(), "application/json", false, xrayResponse.Utf8Value()); 149 | auto outcome = CLIENT.post_failure(requestId.Utf8Value(), response); 150 | return env.Null(); 151 | } 152 | 153 | Napi::Object Init(Napi::Env env, Napi::Object exports) 154 | { 155 | exports.Set(Napi::String::New(env, "next"), Napi::Function::New(env, Next)); 156 | exports.Set(Napi::String::New(env, "done"), Napi::Function::New(env, Done)); 157 | exports.Set(Napi::String::New(env, "error"), Napi::Function::New(env, Error)); 158 | return exports; 159 | } 160 | 161 | NODE_API_MODULE(addon, Init); 162 | -------------------------------------------------------------------------------- /src/types/awslambda.d.ts: -------------------------------------------------------------------------------- 1 | export class HttpResponseStream { 2 | static from(underlyingStream: any, prelude: any): any; 3 | } 4 | 5 | declare global { 6 | namespace awslambda { 7 | function streamifyResponse(handler: any, options: any): any; 8 | let HttpResponseStream: HttpResponseStream; 9 | } 10 | } -------------------------------------------------------------------------------- /src/types/index.d.mts: -------------------------------------------------------------------------------- 1 | export function run(appRootOrHandler: any, handler?: string): Promise; -------------------------------------------------------------------------------- /test/handlers/asyncInit.mjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | let awaited = false; 3 | 4 | awaited = await new Promise((resolve) => setTimeout(() => resolve(true), 900)); 5 | 6 | export const handler = async () => { 7 | if (!awaited) { 8 | throw new Error('The async initialization of this module was not awaited!'); 9 | } 10 | return 'Hi'; 11 | }; 12 | -------------------------------------------------------------------------------- /test/handlers/asyncInitRejection.mjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | let awaited = false; 3 | 4 | class AsyncInitRejectionException extends Error { 5 | constructor(msg) { 6 | super(msg); 7 | this.name = this.constructor.name; 8 | } 9 | } 10 | 11 | awaited = await new Promise((_, reject) => 12 | setTimeout( 13 | () => 14 | reject( 15 | new AsyncInitRejectionException('Oh noes! something bad happened'), 16 | ), 17 | 900, 18 | ), 19 | ); 20 | 21 | export const handler = async () => { 22 | if (!awaited) { 23 | throw new Error('The async initialization of this module was not awaited!'); 24 | } 25 | return 'Hi'; 26 | }; 27 | -------------------------------------------------------------------------------- /test/handlers/async_init_package/cjsModuleInEsmPackage.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | exports.echo = async (event) => { 6 | return event; 7 | }; 8 | -------------------------------------------------------------------------------- /test/handlers/async_init_package/index.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | let awaited = false; 3 | 4 | awaited = await new Promise((resolve) => setTimeout(() => resolve(true), 900)); 5 | 6 | export const handler = async () => { 7 | if (!awaited) { 8 | throw new Error('The async initialization of this module was not awaited!'); 9 | } 10 | return 'Hi'; 11 | }; 12 | -------------------------------------------------------------------------------- /test/handlers/async_init_package/nested/even/more/index.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | exports.handler = () => { 4 | return 42; 5 | }; 6 | -------------------------------------------------------------------------------- /test/handlers/async_init_package/nested/even/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/handlers/async_init_package/nested/even/readme.txt: -------------------------------------------------------------------------------- 1 | package.json exists and does not contain `type: "module"`. -------------------------------------------------------------------------------- /test/handlers/async_init_package/nested/index.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | await new Promise((r) => setTimeout(r, 100)); 4 | 5 | export const handler = async () => { 6 | return 42; 7 | }; 8 | -------------------------------------------------------------------------------- /test/handlers/async_init_package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-init-package-test", 3 | "version": "1.0.0", 4 | "description": "dummy package for the loading esm packages", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "AWS Lambda", 11 | "license": "Apache-2.0" 12 | } 13 | -------------------------------------------------------------------------------- /test/handlers/async_init_type_not_module/index.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | exports.ret42 = () => { 6 | return 42; 7 | }; 8 | -------------------------------------------------------------------------------- /test/handlers/async_init_type_not_module/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Ceci n’est pas une module" 3 | } 4 | -------------------------------------------------------------------------------- /test/handlers/async_init_with_node_modules/node_modules/index.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | "use strict"; 4 | 5 | exports.ret42 = () => { 6 | return 42; 7 | }; 8 | -------------------------------------------------------------------------------- /test/handlers/async_init_with_node_modules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-init-package-test", 3 | "version": "1.0.0", 4 | "description": "dummy package for the loading esm packages", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "AWS Lambda", 11 | "license": "Apache-2.0" 12 | } 13 | -------------------------------------------------------------------------------- /test/handlers/beforeExitBehaviour.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | process.on('beforeExit', () => { 6 | setImmediate(() => console.log('from setImmediate')); 7 | }); 8 | 9 | process.on('beforeExit', () => { 10 | process.nextTick(() => console.log('from process.nextTick')); 11 | }); 12 | 13 | exports.callbackWithTrueFlag = (_event, _cxt, callback) => { 14 | callback(null, 'hello'); 15 | }; 16 | 17 | exports.callbackWithFalseFlag = (_event, cxt, callback) => { 18 | cxt.callbackWaitsForEmptyEventLoop = false; 19 | callback(null, 'hello'); 20 | }; 21 | 22 | exports.asyncFunction = async (_event) => { 23 | return 'hello'; 24 | }; 25 | -------------------------------------------------------------------------------- /test/handlers/cjsModule.cjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | exports.echo = async (event) => { 4 | return event; 5 | }; 6 | -------------------------------------------------------------------------------- /test/handlers/core.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | exports.workingDirectory = async () => process.cwd(); 6 | 7 | exports.echo = async (event) => { 8 | return event; 9 | }; 10 | 11 | exports.ping = async (event) => { 12 | return { 13 | msg: `pong[${event['msg']}]`, 14 | }; 15 | }; 16 | 17 | exports.env = async () => { 18 | return process.env; 19 | }; 20 | 21 | exports.clientContext = async (_, ctx) => { 22 | return ctx.clientContext; 23 | }; 24 | 25 | exports.cognitoIdentity = async (_, _ctx) => { 26 | return context.identity; 27 | }; 28 | 29 | exports.nodePathContainsMajorVersion = async () => { 30 | let majorVersion = process.version.match(/(\d+)/g)[0]; 31 | let expectedNodeString = `node${majorVersion}`; 32 | let nodePath = process.env['NODE_PATH']; 33 | 34 | console.log(nodePath, 'should include', expectedNodeString); 35 | 36 | return { 37 | nodePathIncludesMajorVersion: nodePath.includes(expectedNodeString), 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /test/handlers/defaultHandler.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | module.exports = { 6 | default: () => 42, 7 | }; 8 | -------------------------------------------------------------------------------- /test/handlers/defaultHandlerESM.mjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | export default () => 42; 4 | -------------------------------------------------------------------------------- /test/handlers/esModule.mjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | export const echo = async (event) => { 4 | return event; 5 | }; 6 | -------------------------------------------------------------------------------- /test/handlers/esModuleImports.mjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | import { echo as esModuleEcho } from './esModule.mjs'; 4 | import * as util from 'util'; 5 | import { Buffer } from 'buffer'; 6 | import defaultExport from './defaultHandlerESM.mjs'; 7 | 8 | export const echo = async (event) => { 9 | // Use an arbitrary internal node module, with import star. 10 | console.log(util.format('can import node module: %s+%s', 'yes', 'yes')); 11 | 12 | // Use an arbitrary internal node module. 13 | console.log(Buffer.from('yes')); 14 | 15 | // Use an arbitrary default export. 16 | console.log(defaultExport()); 17 | 18 | return esModuleEcho(event); 19 | }; 20 | -------------------------------------------------------------------------------- /test/handlers/extensionless/esm-extensionless: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | // This should fail because it's ESM syntax in a CJS context 4 | export const handler = async (event) => { 5 | return "This should fail"; 6 | }; 7 | -------------------------------------------------------------------------------- /test/handlers/extensionless/index: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | // This is a CommonJS module without file extension 5 | 6 | module.exports.handler = async (event) => { 7 | return "Hello from extensionless CJS"; 8 | }; 9 | -------------------------------------------------------------------------------- /test/handlers/nestedHandler.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | module.exports = { 6 | nested: { 7 | somethingComplex: { 8 | handler: async () => 'something interesting', 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /test/handlers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "handlers", 3 | "version": "1.0.0", 4 | "description": "test handlers for nodes runtime project", 5 | "main": "index.js", 6 | "dependencies": { 7 | "aws-serverless-express": "^3.3.6", 8 | "body-parser": "^1.18.3", 9 | "cors": "^2.8.5", 10 | "express": "^4.16.4", 11 | "function-bluebird": "file:./fake_node_modules/function-bluebird", 12 | "not-shadowed": "file:./fake_node_modules/not-shadowed", 13 | "precedence": "file:./fake_node_modules/precedence" 14 | }, 15 | "devDependencies": {}, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | }, 19 | "author": "AWS Lambda", 20 | "license": "Apache-2.0", 21 | "eslintConfig": { 22 | "extends": [ 23 | "plugin:prettier/recommended" 24 | ], 25 | "env": { 26 | "node": true, 27 | "mocha": true, 28 | "es6": true 29 | }, 30 | "parserOptions": { 31 | "ecmaVersion": 2020 32 | }, 33 | "rules": { 34 | "strict": [ 35 | "error", 36 | "global" 37 | ], 38 | "indent": [ 39 | "error", 40 | 2 41 | ], 42 | "camelcase": "error", 43 | "no-console": "off", 44 | "no-unused-vars": [ 45 | "error", 46 | { 47 | "argsIgnorePattern": "^_" 48 | } 49 | ] 50 | } 51 | }, 52 | "eslintIgnore": [ 53 | "syntax_error.js", 54 | "node_modules", 55 | "async_init_package" 56 | ], 57 | "prettier": { 58 | "trailingComma": "all", 59 | "tabWidth": 2, 60 | "semi": true, 61 | "singleQuote": true 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/handlers/pkg-less/cjsAndMjs.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | import { someESMFunction } from './esmModule.js'; // ESM import 4 | 5 | module.exports.handler = async (event) => { // CJS export 6 | return someESMFunction(event); 7 | }; 8 | 9 | export const esm = 'This is ESM syntax'; // ESM export 10 | -------------------------------------------------------------------------------- /test/handlers/pkg-less/cjsImportCjs.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | const { getMessage } = require('./cjsModule.cjs') 6 | 7 | exports.handler = async (_event) => { 8 | return getMessage(); 9 | } 10 | -------------------------------------------------------------------------------- /test/handlers/pkg-less/cjsImportESM.cjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | // This static import is not allowed in CJS 6 | import { getMessage } from './esmModule'; 7 | 8 | module.exports.handler = async () => { 9 | return getMessage(); 10 | }; 11 | -------------------------------------------------------------------------------- /test/handlers/pkg-less/cjsInMjs.mjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | // This should fail because it's CJS syntax in a ESM context 6 | module.exports.handler = async (_event) => { 7 | return 'This should fail'; 8 | }; 9 | -------------------------------------------------------------------------------- /test/handlers/pkg-less/cjsModule.cjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | module.exports.getMessage = () => { 6 | return "Hello from CJS!"; 7 | }; 8 | -------------------------------------------------------------------------------- /test/handlers/pkg-less/esmImportCjs.mjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | import { getMessage } from './cjsModule.cjs'; 4 | 5 | export const handler = async (_event) => { 6 | return getMessage(); 7 | }; 8 | -------------------------------------------------------------------------------- /test/handlers/pkg-less/esmInCjs.cjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | // This should fail because it's ESM syntax in a CJS context 4 | export const handler = async (_event) => { 5 | return 'This should fail'; 6 | }; 7 | -------------------------------------------------------------------------------- /test/handlers/pkg-less/esmModule.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | export const handler = async (_event) => { 4 | return 'Hello from ESM.js'; 5 | }; 6 | 7 | export const getMessage = () => { 8 | return "Hello from ESM!"; 9 | }; 10 | -------------------------------------------------------------------------------- /test/handlers/pkg-less/esmRequireCjs.mjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | const { getMessage } = require('./cjsModule.cjs') 4 | 5 | export const handler = async (_event) => { 6 | return getMessage(); 7 | }; 8 | -------------------------------------------------------------------------------- /test/handlers/pkg/type-cjs/cjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | // This is a CommonJS module without file extension 5 | 6 | module.exports.handler = async (event) => { 7 | return "Hello from extensionless CJS"; 8 | }; 9 | -------------------------------------------------------------------------------- /test/handlers/pkg/type-cjs/cjsModule.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | module.exports.handler = async (_event) => { 6 | return 'Hello from CJS.js'; 7 | }; 8 | -------------------------------------------------------------------------------- /test/handlers/pkg/type-cjs/esm: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | // This should fail because it's ESM syntax in a CJS context 4 | export const handler = async (event) => { 5 | return "This should fail"; 6 | }; 7 | -------------------------------------------------------------------------------- /test/handlers/pkg/type-cjs/esmModule.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | // This should fail because it's ESM syntax in a CJS context 4 | export const handler = async (_event) => { 5 | return 'This should fail'; 6 | }; 7 | -------------------------------------------------------------------------------- /test/handlers/pkg/type-cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /test/handlers/pkg/type-esm/cjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | // This is a CommonJS module without file extension 5 | 6 | module.exports.handler = async (event) => { 7 | return "Hello from extensionless CJS"; 8 | }; 9 | -------------------------------------------------------------------------------- /test/handlers/pkg/type-esm/cjsModule.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | // This should fail because it's CJS syntax in a ESM context 6 | module.exports.handler = async (_event) => { 7 | return 'This should fail'; 8 | }; 9 | -------------------------------------------------------------------------------- /test/handlers/pkg/type-esm/esm: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | // This should fail because it's ESM syntax in a CJS context 4 | export const handler = async (event) => { 5 | return "This should fail"; 6 | }; 7 | -------------------------------------------------------------------------------- /test/handlers/pkg/type-esm/esmModule.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | export const handler = async (_event) => { 4 | return 'Hello from ESM.js'; 5 | }; 6 | -------------------------------------------------------------------------------- /test/handlers/pkg/type-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /test/handlers/precedence: -------------------------------------------------------------------------------- 1 | /** Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.*/ 2 | 3 | 'use strict'; 4 | 5 | module.exports.handler = async () => "I don't have a .js file suffix"; 6 | 7 | -------------------------------------------------------------------------------- /test/handlers/precedence.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | module.exports.handler = async () => 'I do have a .js file suffix'; 6 | -------------------------------------------------------------------------------- /test/handlers/precedence.json: -------------------------------------------------------------------------------- 1 | { 2 | "handler": "this should never be used" 3 | } 4 | -------------------------------------------------------------------------------- /test/handlers/precedence.mjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | module.exports.handler = async () => 'I do have a .mjs file suffix'; 6 | -------------------------------------------------------------------------------- /test/handlers/precedence.node: -------------------------------------------------------------------------------- 1 | this isn't actually a node extension 2 | -------------------------------------------------------------------------------- /test/handlers/precedenceJsVsMjs.js: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | module.exports.handler = async () => 'I do have a .js file suffix'; 6 | -------------------------------------------------------------------------------- /test/handlers/precedenceJsVsMjs.mjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | export const handler = async () => 'I do have a .mjs file suffix'; 6 | -------------------------------------------------------------------------------- /test/handlers/precedenceMjsVsCjs.cjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | module.exports.handler = async () => 'I do have a .cjs file suffix'; 6 | -------------------------------------------------------------------------------- /test/handlers/precedenceMjsVsCjs.mjs: -------------------------------------------------------------------------------- 1 | /** Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | 'use strict'; 4 | 5 | export const handler = async () => 'I do have a .mjs file suffix'; 6 | -------------------------------------------------------------------------------- /test/integration/codebuild-local/Dockerfile.agent: -------------------------------------------------------------------------------- 1 | FROM amazonlinux:2 2 | 3 | RUN amazon-linux-extras enable docker && \ 4 | yum clean metadata && \ 5 | yum install -y docker tar 6 | -------------------------------------------------------------------------------- /test/integration/codebuild-local/codebuild_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This file is copied from https://github.com/aws/aws-codebuild-docker-images/blob/f0912e4b16e427da35351fc102f0f56f4ceb938a/local_builds/codebuild_build.sh 3 | 4 | function allOSRealPath() { 5 | if isOSWindows 6 | then 7 | path="" 8 | case $1 in 9 | .* ) path="$PWD/${1#./}" ;; 10 | /* ) path="$1" ;; 11 | * ) path="/$1" ;; 12 | esac 13 | 14 | echo "/$path" | sed -e 's/\\/\//g' -e 's/://' -e 's/./\U&/3' 15 | else 16 | case $1 in 17 | /* ) echo "$1"; exit;; 18 | * ) echo "$PWD/${1#./}"; exit;; 19 | esac 20 | fi 21 | } 22 | 23 | function isOSWindows() { 24 | if [ $OSTYPE == "msys" ] 25 | then 26 | return 0 27 | else 28 | return 1 29 | fi 30 | } 31 | 32 | function usage { 33 | echo "usage: codebuild_build.sh [-i image_name] [-a artifact_output_directory] [options]" 34 | echo "Required:" 35 | echo " -i Used to specify the customer build container image." 36 | echo " -a Used to specify an artifact output directory." 37 | echo "Options:" 38 | echo " -l IMAGE Used to override the default local agent image." 39 | echo " -r Used to specify a report output directory." 40 | echo " -s Used to specify source information. Defaults to the current working directory for primary source." 41 | echo " * First (-s) is for primary source" 42 | echo " * Use additional (-s) in : format for secondary source" 43 | echo " * For sourceIdentifier, use a value that is fewer than 128 characters and contains only alphanumeric characters and underscores" 44 | echo " -c Use the AWS configuration and credentials from your local host. This includes ~/.aws and any AWS_* environment variables." 45 | echo " -p Used to specify the AWS CLI Profile." 46 | echo " -b FILE Used to specify a buildspec override file. Defaults to buildspec.yml in the source directory." 47 | echo " -m Used to mount the source directory to the customer build container directly." 48 | echo " -d Used to run the build container in docker privileged mode." 49 | echo " -e FILE Used to specify a file containing environment variables." 50 | echo " (-e) File format expectations:" 51 | echo " * Each line is in VAR=VAL format" 52 | echo " * Lines beginning with # are processed as comments and ignored" 53 | echo " * Blank lines are ignored" 54 | echo " * File can be of type .env or .txt" 55 | echo " * There is no special handling of quotation marks, meaning they will be part of the VAL" 56 | exit 1 57 | } 58 | 59 | image_flag=false 60 | artifact_flag=false 61 | awsconfig_flag=false 62 | mount_src_dir_flag=false 63 | docker_privileged_mode_flag=false 64 | 65 | while getopts "cmdi:a:r:s:b:e:l:p:h" opt; do 66 | case $opt in 67 | i ) image_flag=true; image_name=$OPTARG;; 68 | a ) artifact_flag=true; artifact_dir=$OPTARG;; 69 | r ) report_dir=$OPTARG;; 70 | b ) buildspec=$OPTARG;; 71 | c ) awsconfig_flag=true;; 72 | m ) mount_src_dir_flag=true;; 73 | d ) docker_privileged_mode_flag=true;; 74 | s ) source_dirs+=("$OPTARG");; 75 | e ) environment_variable_file=$OPTARG;; 76 | l ) local_agent_image=$OPTARG;; 77 | p ) aws_profile=$OPTARG;; 78 | h ) usage; exit;; 79 | \? ) echo "Unknown option: -$OPTARG" >&2; exit 1;; 80 | : ) echo "Missing option argument for -$OPTARG" >&2; exit 1;; 81 | * ) echo "Invalid option: -$OPTARG" >&2; exit 1;; 82 | esac 83 | done 84 | 85 | if ! $image_flag 86 | then 87 | echo "The image name flag (-i) must be included for a build to run" >&2 88 | fi 89 | 90 | if ! $artifact_flag 91 | then 92 | echo "The artifact directory (-a) must be included for a build to run" >&2 93 | fi 94 | 95 | if ! $image_flag || ! $artifact_flag 96 | then 97 | exit 1 98 | fi 99 | 100 | docker_command="docker run " 101 | if isOSWindows 102 | then 103 | docker_command+="-v //var/run/docker.sock:/var/run/docker.sock -e " 104 | else 105 | docker_command+="-v /var/run/docker.sock:/var/run/docker.sock -e " 106 | fi 107 | 108 | docker_command+="\"IMAGE_NAME=$image_name\" -e \ 109 | \"ARTIFACTS=$(allOSRealPath "$artifact_dir")\"" 110 | 111 | if [ -n "$report_dir" ] 112 | then 113 | docker_command+=" -e \"REPORTS=$(allOSRealPath "$report_dir")\"" 114 | fi 115 | 116 | if [ -z "$source_dirs" ] 117 | then 118 | docker_command+=" -e \"SOURCE=$(allOSRealPath "$PWD")\"" 119 | else 120 | for index in "${!source_dirs[@]}"; do 121 | if [ $index -eq 0 ] 122 | then 123 | docker_command+=" -e \"SOURCE=$(allOSRealPath "${source_dirs[$index]}")\"" 124 | else 125 | identifier=${source_dirs[$index]%%:*} 126 | src_dir=$(allOSRealPath "${source_dirs[$index]#*:}") 127 | 128 | docker_command+=" -e \"SECONDARY_SOURCE_$index=$identifier:$src_dir\"" 129 | fi 130 | done 131 | fi 132 | 133 | if [ -n "$buildspec" ] 134 | then 135 | docker_command+=" -e \"BUILDSPEC=$(allOSRealPath "$buildspec")\"" 136 | fi 137 | 138 | if [ -n "$environment_variable_file" ] 139 | then 140 | environment_variable_file_path=$(allOSRealPath "$environment_variable_file") 141 | environment_variable_file_dir=$(dirname "$environment_variable_file_path") 142 | environment_variable_file_basename=$(basename "$environment_variable_file") 143 | docker_command+=" -v \"$environment_variable_file_dir:/LocalBuild/envFile/\" -e \"ENV_VAR_FILE=$environment_variable_file_basename\"" 144 | fi 145 | 146 | if [ -n "$local_agent_image" ] 147 | then 148 | docker_command+=" -e \"LOCAL_AGENT_IMAGE_NAME=$local_agent_image\"" 149 | fi 150 | 151 | if $awsconfig_flag 152 | then 153 | if [ -d "$HOME/.aws" ] 154 | then 155 | configuration_file_path=$(allOSRealPath "$HOME/.aws") 156 | docker_command+=" -e \"AWS_CONFIGURATION=$configuration_file_path\"" 157 | else 158 | docker_command+=" -e \"AWS_CONFIGURATION=NONE\"" 159 | fi 160 | 161 | if [ -n "$aws_profile" ] 162 | then 163 | docker_command+=" -e \"AWS_PROFILE=$aws_profile\"" 164 | fi 165 | 166 | docker_command+="$(env | grep ^AWS_ | while read -r line; do echo " -e \"$line\""; done )" 167 | fi 168 | 169 | if $mount_src_dir_flag 170 | then 171 | docker_command+=" -e \"MOUNT_SOURCE_DIRECTORY=TRUE\"" 172 | fi 173 | 174 | if $docker_privileged_mode_flag 175 | then 176 | docker_command+=" -e \"DOCKER_PRIVILEGED_MODE=TRUE\"" 177 | fi 178 | 179 | if isOSWindows 180 | then 181 | docker_command+=" -e \"INITIATOR=$USERNAME\"" 182 | else 183 | docker_command+=" -e \"INITIATOR=$USER\"" 184 | fi 185 | 186 | if [ -n "$local_agent_image" ] 187 | then 188 | docker_command+=" $local_agent_image" 189 | else 190 | docker_command+=" public.ecr.aws/codebuild/local-builds:latest" 191 | fi 192 | 193 | # Note we do not expose the AWS_SECRET_ACCESS_KEY or the AWS_SESSION_TOKEN 194 | exposed_command=$docker_command 195 | secure_variables=( "AWS_SECRET_ACCESS_KEY=" "AWS_SESSION_TOKEN=") 196 | for variable in "${secure_variables[@]}" 197 | do 198 | exposed_command="$(echo $exposed_command | sed "s/\($variable\)[^ ]*/\1********\"/")" 199 | done 200 | 201 | echo "Build Command:" 202 | echo "" 203 | echo $exposed_command 204 | echo "" 205 | 206 | eval $docker_command 207 | -------------------------------------------------------------------------------- /test/integration/codebuild-local/test_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | set -euo pipefail 5 | 6 | CODEBUILD_IMAGE_TAG="${CODEBUILD_IMAGE_TAG:-al2/x86_64/standard/3.0}" 7 | DISTRO="${DISTRO:=""}" 8 | DRYRUN="${DRYRUN-0}" 9 | 10 | function usage { 11 | echo "usage: test_all.sh buildspec_yml_dir" 12 | echo "Runs all buildspec build-matrix combinations via test_one.sh." 13 | echo "Required:" 14 | echo " buildspec_yml_dir Used to specify the CodeBuild buildspec template file." 15 | } 16 | 17 | do_one_yaml() { 18 | local -r YML="$1" 19 | 20 | OS_DISTRIBUTION=$(grep -oE 'OS_DISTRIBUTION:\s*(\S+)' "$YML" | cut -d' ' -f2) 21 | DISTRO_VERSIONS=$(sed '1,/DISTRO_VERSION/d;/RUNTIME_VERSION/,$d' "$YML" | tr -d '\-" ') 22 | RUNTIME_VERSIONS=$(sed '1,/RUNTIME_VERSION/d;/phases/,$d' "$YML" | sed '/#.*$/d' | tr -d '\-" ') 23 | 24 | for DISTRO_VERSION in $DISTRO_VERSIONS ; do 25 | for RUNTIME_VERSION in $RUNTIME_VERSIONS ; do 26 | if (( DRYRUN == 1 )) ; then 27 | echo DRYRUN test_one_combination "$YML" "$OS_DISTRIBUTION" "$DISTRO_VERSION" "$RUNTIME_VERSION" 28 | else 29 | test_one_combination "$YML" "$OS_DISTRIBUTION" "$DISTRO_VERSION" "$RUNTIME_VERSION" 30 | fi 31 | done 32 | done 33 | } 34 | 35 | test_one_combination() { 36 | local -r YML="$1" 37 | local -r OS_DISTRIBUTION="$2" 38 | local -r DISTRO_VERSION="$3" 39 | local -r RUNTIME_VERSION="$4" 40 | 41 | echo Testing: 42 | echo " BUILDSPEC" "$YML" 43 | echo " with" "$OS_DISTRIBUTION"-"$DISTRO_VERSION" "$RUNTIME_VERSION" 44 | 45 | "$(dirname "$0")"/test_one.sh "$YML" "$OS_DISTRIBUTION" "$DISTRO_VERSION" "$RUNTIME_VERSION" \ 46 | > >(sed "s/^/$OS_DISTRIBUTION$DISTRO_VERSION-$RUNTIME_VERSION: /") 2> >(sed "s/^/$OS_DISTRIBUTION-$DISTRO_VERSION:$RUNTIME_VERSION: /" >&2) 47 | } 48 | 49 | main() { 50 | if (( $# != 1 && $# != 2)); then 51 | >&2 echo "Invalid number of parameters." 52 | usage 53 | exit 1 54 | fi 55 | 56 | BUILDSPEC_YML_DIR="$1" 57 | HAS_YML=0 58 | for f in "$BUILDSPEC_YML_DIR"/*"$DISTRO"*.yml ; do 59 | [ -f "$f" ] || continue; 60 | do_one_yaml "$f" 61 | HAS_YML=1 62 | done 63 | 64 | if (( HAS_YML == 0 )); then 65 | >&2 echo At least one buildspec is required. 66 | exit 2 67 | fi 68 | } 69 | 70 | main "$@" 71 | -------------------------------------------------------------------------------- /test/integration/codebuild-local/test_one.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | set -euo pipefail 5 | 6 | CODEBUILD_IMAGE_TAG="${CODEBUILD_IMAGE_TAG:-al2/x86_64/standard/3.0}" 7 | 8 | function usage { 9 | >&2 echo "usage: test_one.sh buildspec_yml os_distribution distro_version runtime_version [env]" 10 | >&2 echo "Runs one buildspec version combination from a build-matrix buildspec." 11 | >&2 echo "Required:" 12 | >&2 echo " buildspec_yml Used to specify the CodeBuild buildspec template file." 13 | >&2 echo " os_distribution Used to specify the OS distribution to build." 14 | >&2 echo " distro_version Used to specify the distro version of ." 15 | >&2 echo " runtime_version Used to specify the runtime version to test on the selected ." 16 | >&2 echo "Optional:" 17 | >&2 echo " env Additional environment variables file." 18 | } 19 | 20 | main() { 21 | if (( $# != 3 && $# != 4)); then 22 | >&2 echo "Invalid number of parameters." 23 | usage 24 | exit 1 25 | fi 26 | 27 | BUILDSPEC_YML="$1" 28 | OS_DISTRIBUTION="$2" 29 | DISTRO_VERSION="$3" 30 | RUNTIME_VERSION="$4" 31 | EXTRA_ENV="${5-}" 32 | 33 | CODEBUILD_TEMP_DIR=$(mktemp -d codebuild."$OS_DISTRIBUTION"-"$DISTRO_VERSION"-"$RUNTIME_VERSION".XXXXXXXXXX) 34 | trap 'rm -rf $CODEBUILD_TEMP_DIR' EXIT 35 | 36 | # Create an env file for codebuild_build. 37 | ENVFILE="$CODEBUILD_TEMP_DIR/.env" 38 | if [ -f "$EXTRA_ENV" ]; then 39 | cat "$EXTRA_ENV" > "$ENVFILE" 40 | fi 41 | { 42 | echo "" 43 | echo "OS_DISTRIBUTION=$OS_DISTRIBUTION" 44 | echo "DISTRO_VERSION=$DISTRO_VERSION" 45 | echo "RUNTIME_VERSION=$RUNTIME_VERSION" 46 | } >> "$ENVFILE" 47 | 48 | ARTIFACTS_DIR="$CODEBUILD_TEMP_DIR/artifacts" 49 | mkdir -p "$ARTIFACTS_DIR" 50 | 51 | # Run CodeBuild local agent. 52 | "$(dirname "$0")"/codebuild_build.sh \ 53 | -i "$CODEBUILD_IMAGE_TAG" \ 54 | -a "$ARTIFACTS_DIR" \ 55 | -e "$ENVFILE" \ 56 | -b "$BUILDSPEC_YML" 57 | } 58 | 59 | main "$@" 60 | -------------------------------------------------------------------------------- /test/integration/codebuild/buildspec.os.alpine.1.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: alpine 6 | NPX_BINARY_LOCATION: "/usr/local/bin/npx" 7 | batch: 8 | build-matrix: 9 | static: 10 | ignore-failure: false 11 | env: 12 | privileged-mode: true 13 | dynamic: 14 | env: 15 | variables: 16 | DISTRO_VERSION: 17 | - "3.16" 18 | RUNTIME_VERSION: 19 | - "16" 20 | - "18" 21 | phases: 22 | pre_build: 23 | commands: 24 | - export IMAGE_TAG="nodejs-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 25 | - echo "Extracting and including the Runtime Interface Emulator" 26 | - SCRATCH_DIR=".scratch" 27 | - mkdir "${SCRATCH_DIR}" 28 | - ARCHITECTURE=$(arch) 29 | - > 30 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 31 | RIE="aws-lambda-rie" 32 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 33 | RIE="aws-lambda-rie-arm64" 34 | else 35 | echo "Architecture $ARCHITECTURE is not currently supported." 36 | exit 1 37 | fi 38 | - tar -xvf test/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 39 | - > 40 | cp "test/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 41 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 42 | - > 43 | echo "RUN apk add curl" >> \ 44 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 45 | - > 46 | echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ 47 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 48 | - > 49 | if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; 50 | then 51 | echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." 52 | else 53 | echo "Performing DockerHub login . . ." 54 | docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD 55 | fi 56 | - echo "Building image ${IMAGE_TAG}" 57 | - > 58 | docker build . \ 59 | -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ 60 | -t "${IMAGE_TAG}" \ 61 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 62 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" 63 | build: 64 | commands: 65 | - set -x 66 | - echo "Running Image ${IMAGE_TAG}" 67 | - docker network create "${OS_DISTRIBUTION}-network" 68 | - > 69 | docker run \ 70 | --detach \ 71 | --name "${OS_DISTRIBUTION}-app" \ 72 | --network "${OS_DISTRIBUTION}-network" \ 73 | --entrypoint="" \ 74 | "${IMAGE_TAG}" \ 75 | sh -c "/usr/bin/${RIE} ${NPX_BINARY_LOCATION} aws-lambda-ric index.handler" 76 | - sleep 2 77 | - > 78 | docker run \ 79 | --name "${OS_DISTRIBUTION}-tester" \ 80 | --env "TARGET=${OS_DISTRIBUTION}-app" \ 81 | --network "${OS_DISTRIBUTION}-network" \ 82 | --entrypoint="" \ 83 | "${IMAGE_TAG}" \ 84 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 85 | - actual="$(docker logs --tail 1 "${OS_DISTRIBUTION}-tester" | xargs)" 86 | - expected='success' 87 | - | 88 | echo "Response: ${actual}" 89 | if [[ "$actual" != "$expected" ]]; then 90 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 91 | exit -1 92 | fi 93 | finally: 94 | - | 95 | echo "---------Container Logs: ${OS_DISTRIBUTION}-app----------" 96 | echo 97 | docker logs "${OS_DISTRIBUTION}-app" || true 98 | echo 99 | echo "---------------------------------------------------" 100 | echo "--------Container Logs: ${OS_DISTRIBUTION}-tester--------" 101 | echo 102 | docker logs "${OS_DISTRIBUTION}-tester" || true 103 | echo 104 | echo "---------------------------------------------------" 105 | - echo "Cleaning up..." 106 | - docker stop "${OS_DISTRIBUTION}-app" || true 107 | - docker rm --force "${OS_DISTRIBUTION}-app" || true 108 | - docker stop "${OS_DISTRIBUTION}-tester" || true 109 | - docker rm --force "${OS_DISTRIBUTION}-tester" || true 110 | - docker network rm "${OS_DISTRIBUTION}-network" || true 111 | - docker rmi "${IMAGE_TAG}" || true 112 | -------------------------------------------------------------------------------- /test/integration/codebuild/buildspec.os.alpine.2.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: alpine 6 | NODE_BINARY_LOCATION: "/usr/local/bin/node" 7 | NPX_BINARY_LOCATION: "/usr/local/bin/npx" 8 | batch: 9 | build-matrix: 10 | static: 11 | ignore-failure: false 12 | env: 13 | type: LINUX_CONTAINER 14 | privileged-mode: true 15 | dynamic: 16 | env: 17 | variables: 18 | DISTRO_VERSION: 19 | - "3.18" 20 | RUNTIME_VERSION: 21 | - "20" 22 | phases: 23 | pre_build: 24 | commands: 25 | - export IMAGE_TAG="nodejs-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 26 | - echo "Extracting and including the Runtime Interface Emulator" 27 | - SCRATCH_DIR=".scratch" 28 | - mkdir "${SCRATCH_DIR}" 29 | - tar -xvf test/integration/resources/aws-lambda-rie.tar.gz --directory "${SCRATCH_DIR}" 30 | - > 31 | cp "test/integration/docker/Dockerfile.programmatic.${OS_DISTRIBUTION}" \ 32 | "${SCRATCH_DIR}/Dockerfile.programmatic.${OS_DISTRIBUTION}.tmp" 33 | - > 34 | echo "RUN apk add curl" >> \ 35 | "${SCRATCH_DIR}/Dockerfile.programmatic.${OS_DISTRIBUTION}.tmp" 36 | - > 37 | echo "COPY ${SCRATCH_DIR}/aws-lambda-rie /usr/bin/aws-lambda-rie" >> \ 38 | "${SCRATCH_DIR}/Dockerfile.programmatic.${OS_DISTRIBUTION}.tmp" 39 | - > 40 | if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; 41 | then 42 | echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." 43 | else 44 | echo "Performing DockerHub login . . ." 45 | docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD 46 | fi 47 | - echo "Building image ${IMAGE_TAG}" 48 | - > 49 | docker build . \ 50 | -f "${SCRATCH_DIR}/Dockerfile.programmatic.${OS_DISTRIBUTION}.tmp" \ 51 | -t "${IMAGE_TAG}" \ 52 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 53 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" 54 | build: 55 | commands: 56 | - set -x 57 | - echo "Running Image ${IMAGE_TAG}" 58 | - docker network create "${OS_DISTRIBUTION}-network" 59 | - > 60 | docker run \ 61 | --detach \ 62 | -e "NODE_BINARY_LOCATION=${NODE_BINARY_LOCATION}" \ 63 | --name "${OS_DISTRIBUTION}-app" \ 64 | --network "${OS_DISTRIBUTION}-network" \ 65 | --entrypoint="" \ 66 | "${IMAGE_TAG}" \ 67 | sh -c '/usr/bin/aws-lambda-rie ${NODE_BINARY_LOCATION} index.mjs' 68 | - sleep 2 69 | - > 70 | docker run \ 71 | --name "${OS_DISTRIBUTION}-tester" \ 72 | --env "TARGET=${OS_DISTRIBUTION}-app" \ 73 | --network "${OS_DISTRIBUTION}-network" \ 74 | --entrypoint="" \ 75 | "${IMAGE_TAG}" \ 76 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 77 | - actual="$(docker logs --tail 1 "${OS_DISTRIBUTION}-tester" | xargs)" 78 | - expected='success' 79 | - | 80 | echo "Response: ${actual}" 81 | if [[ "$actual" != "$expected" ]]; then 82 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 83 | echo "---------Container Logs: ${OS_DISTRIBUTION}-app----------" 84 | echo 85 | docker logs "${OS_DISTRIBUTION}-app" 86 | echo 87 | echo "---------------------------------------------------" 88 | echo "--------Container Logs: ${OS_DISTRIBUTION}-tester--------" 89 | echo 90 | docker logs "${OS_DISTRIBUTION}-tester" 91 | echo 92 | echo "---------------------------------------------------" 93 | exit -1 94 | fi 95 | finally: 96 | - echo "Cleaning up..." 97 | - docker stop "${OS_DISTRIBUTION}-app" || true 98 | - docker rm --force "${OS_DISTRIBUTION}-app" || true 99 | - docker stop "${OS_DISTRIBUTION}-tester" || true 100 | - docker rm --force "${OS_DISTRIBUTION}-tester" || true 101 | - docker network rm "${OS_DISTRIBUTION}-network" || true 102 | - docker rmi "${IMAGE_TAG}" || true 103 | -------------------------------------------------------------------------------- /test/integration/codebuild/buildspec.os.alpine.3.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: alpine 6 | NPX_BINARY_LOCATION: "/usr/local/bin/npx" 7 | batch: 8 | build-matrix: 9 | static: 10 | ignore-failure: false 11 | env: 12 | privileged-mode: true 13 | dynamic: 14 | env: 15 | variables: 16 | DISTRO_VERSION: 17 | - "3.17" 18 | - "3.18" 19 | RUNTIME_VERSION: 20 | - "16" 21 | - "18" 22 | - "20" 23 | phases: 24 | pre_build: 25 | commands: 26 | - export IMAGE_TAG="nodejs-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 27 | - echo "Extracting and including the Runtime Interface Emulator" 28 | - SCRATCH_DIR=".scratch" 29 | - mkdir "${SCRATCH_DIR}" 30 | - ARCHITECTURE=$(arch) 31 | - > 32 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 33 | RIE="aws-lambda-rie" 34 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 35 | RIE="aws-lambda-rie-arm64" 36 | else 37 | echo "Architecture $ARCHITECTURE is not currently supported." 38 | exit 1 39 | fi 40 | - tar -xvf test/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 41 | - > 42 | cp "test/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 43 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 44 | - > 45 | echo "RUN apk add curl" >> \ 46 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 47 | - > 48 | echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ 49 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 50 | - > 51 | if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; 52 | then 53 | echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." 54 | else 55 | echo "Performing DockerHub login . . ." 56 | docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD 57 | fi 58 | - echo "Building image ${IMAGE_TAG}" 59 | - > 60 | docker build . \ 61 | -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ 62 | -t "${IMAGE_TAG}" \ 63 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 64 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" 65 | build: 66 | commands: 67 | - set -x 68 | - echo "Running Image ${IMAGE_TAG}" 69 | - docker network create "${OS_DISTRIBUTION}-network" 70 | - > 71 | docker run \ 72 | --detach \ 73 | --name "${OS_DISTRIBUTION}-app" \ 74 | --network "${OS_DISTRIBUTION}-network" \ 75 | --entrypoint="" \ 76 | "${IMAGE_TAG}" \ 77 | sh -c "/usr/bin/${RIE} ${NPX_BINARY_LOCATION} aws-lambda-ric index.handler" 78 | - sleep 2 79 | - > 80 | docker run \ 81 | --name "${OS_DISTRIBUTION}-tester" \ 82 | --env "TARGET=${OS_DISTRIBUTION}-app" \ 83 | --network "${OS_DISTRIBUTION}-network" \ 84 | --entrypoint="" \ 85 | "${IMAGE_TAG}" \ 86 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 87 | - actual="$(docker logs --tail 1 "${OS_DISTRIBUTION}-tester" | xargs)" 88 | - expected='success' 89 | - | 90 | echo "Response: ${actual}" 91 | if [[ "$actual" != "$expected" ]]; then 92 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 93 | exit -1 94 | fi 95 | finally: 96 | - | 97 | echo "---------Container Logs: ${OS_DISTRIBUTION}-app----------" 98 | echo 99 | docker logs "${OS_DISTRIBUTION}-app" || true 100 | echo 101 | echo "---------------------------------------------------" 102 | echo "--------Container Logs: ${OS_DISTRIBUTION}-tester--------" 103 | echo 104 | docker logs "${OS_DISTRIBUTION}-tester" || true 105 | echo 106 | echo "---------------------------------------------------" 107 | - echo "Cleaning up..." 108 | - docker stop "${OS_DISTRIBUTION}-app" || true 109 | - docker rm --force "${OS_DISTRIBUTION}-app" || true 110 | - docker stop "${OS_DISTRIBUTION}-tester" || true 111 | - docker rm --force "${OS_DISTRIBUTION}-tester" || true 112 | - docker network rm "${OS_DISTRIBUTION}-network" || true 113 | - docker rmi "${IMAGE_TAG}" || true 114 | -------------------------------------------------------------------------------- /test/integration/codebuild/buildspec.os.amazonlinux.2.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: amazonlinux 6 | NPX_BINARY_LOCATION: "/usr/bin/npx" 7 | batch: 8 | build-matrix: 9 | static: 10 | ignore-failure: false 11 | env: 12 | privileged-mode: true 13 | dynamic: 14 | env: 15 | variables: 16 | DISTRO_VERSION: 17 | - "2" 18 | RUNTIME_VERSION: 19 | - "14" 20 | - "16" 21 | phases: 22 | pre_build: 23 | commands: 24 | - export IMAGE_TAG="nodejs-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 25 | - echo "Extracting and including the Runtime Interface Emulator" 26 | - SCRATCH_DIR=".scratch" 27 | - mkdir "${SCRATCH_DIR}" 28 | - ARCHITECTURE=$(arch) 29 | - > 30 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 31 | RIE="aws-lambda-rie" 32 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 33 | RIE="aws-lambda-rie-arm64" 34 | else 35 | echo "Architecture $ARCHITECTURE is not currently supported." 36 | exit 1 37 | fi 38 | - tar -xvf test/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 39 | - > 40 | cp "test/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 41 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 42 | - > 43 | echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ 44 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 45 | - > 46 | if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; 47 | then 48 | echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." 49 | else 50 | echo "Performing DockerHub login . . ." 51 | docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD 52 | fi 53 | - echo "Building image ${IMAGE_TAG}" 54 | - > 55 | docker build . \ 56 | -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ 57 | -t "${IMAGE_TAG}" \ 58 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 59 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ 60 | --build-arg ARCHITECTURE="${ARCHITECTURE}" 61 | build: 62 | commands: 63 | - set -x 64 | - echo "Running Image ${IMAGE_TAG}" 65 | - docker network create "${OS_DISTRIBUTION}-network" 66 | - > 67 | docker run \ 68 | --detach \ 69 | --name "${OS_DISTRIBUTION}-app" \ 70 | --network "${OS_DISTRIBUTION}-network" \ 71 | --entrypoint="" \ 72 | "${IMAGE_TAG}" \ 73 | sh -c "/usr/bin/${RIE} ${NPX_BINARY_LOCATION} aws-lambda-ric index.handler" 74 | - sleep 2 75 | - > 76 | docker run \ 77 | --name "${OS_DISTRIBUTION}-tester" \ 78 | --env "TARGET=${OS_DISTRIBUTION}-app" \ 79 | --network "${OS_DISTRIBUTION}-network" \ 80 | --entrypoint="" \ 81 | "${IMAGE_TAG}" \ 82 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 83 | - actual="$(docker logs --tail 1 "${OS_DISTRIBUTION}-tester" | xargs)" 84 | - expected='success' 85 | - | 86 | echo "Response: ${actual}" 87 | if [[ "$actual" != "$expected" ]]; then 88 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 89 | exit -1 90 | fi 91 | finally: 92 | - | 93 | echo "---------Container Logs: ${OS_DISTRIBUTION}-app----------" 94 | echo 95 | docker logs "${OS_DISTRIBUTION}-app" || true 96 | echo 97 | echo "---------------------------------------------------" 98 | echo "--------Container Logs: ${OS_DISTRIBUTION}-tester--------" 99 | echo 100 | docker logs "${OS_DISTRIBUTION}-tester" || true 101 | echo 102 | echo "---------------------------------------------------" 103 | - echo "Cleaning up..." 104 | - docker stop "${OS_DISTRIBUTION}-app" || true 105 | - docker rm --force "${OS_DISTRIBUTION}-app" || true 106 | - docker stop "${OS_DISTRIBUTION}-tester" || true 107 | - docker rm --force "${OS_DISTRIBUTION}-tester" || true 108 | - docker network rm "${OS_DISTRIBUTION}-network" || true 109 | - docker rmi "${IMAGE_TAG}" || true 110 | -------------------------------------------------------------------------------- /test/integration/codebuild/buildspec.os.amazonlinux.2023.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: amazonlinux 6 | NPX_BINARY_LOCATION: "/usr/bin/npx" 7 | batch: 8 | build-matrix: 9 | static: 10 | ignore-failure: false 11 | env: 12 | privileged-mode: true 13 | dynamic: 14 | env: 15 | variables: 16 | DISTRO_VERSION: 17 | - "2023" 18 | RUNTIME_VERSION: 19 | - "18" 20 | - "20" 21 | phases: 22 | pre_build: 23 | commands: 24 | - export IMAGE_TAG="nodejs-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 25 | - echo "Extracting and including the Runtime Interface Emulator" 26 | - SCRATCH_DIR=".scratch" 27 | - mkdir "${SCRATCH_DIR}" 28 | - ARCHITECTURE=$(arch) 29 | - > 30 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 31 | RIE="aws-lambda-rie" 32 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 33 | RIE="aws-lambda-rie-arm64" 34 | else 35 | echo "Architecture $ARCHITECTURE is not currently supported." 36 | exit 1 37 | fi 38 | - tar -xvf test/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 39 | - > 40 | cp "test/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 41 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 42 | - > 43 | echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ 44 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 45 | - > 46 | if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; 47 | then 48 | echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." 49 | else 50 | echo "Performing DockerHub login . . ." 51 | docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD 52 | fi 53 | - echo "Building image ${IMAGE_TAG}" 54 | - > 55 | docker build . \ 56 | -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ 57 | -t "${IMAGE_TAG}" \ 58 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 59 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ 60 | --build-arg ARCHITECTURE="${ARCHITECTURE}" 61 | build: 62 | commands: 63 | - set -x 64 | - echo "Running Image ${IMAGE_TAG}" 65 | - docker network create "${OS_DISTRIBUTION}-network" 66 | - > 67 | docker run \ 68 | --detach \ 69 | --name "${OS_DISTRIBUTION}-app" \ 70 | --network "${OS_DISTRIBUTION}-network" \ 71 | --entrypoint="" \ 72 | "${IMAGE_TAG}" \ 73 | sh -c "/usr/bin/${RIE} ${NPX_BINARY_LOCATION} aws-lambda-ric index.handler" 74 | - sleep 2 75 | - > 76 | docker run \ 77 | --name "${OS_DISTRIBUTION}-tester" \ 78 | --env "TARGET=${OS_DISTRIBUTION}-app" \ 79 | --network "${OS_DISTRIBUTION}-network" \ 80 | --entrypoint="" \ 81 | "${IMAGE_TAG}" \ 82 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 83 | - actual="$(docker logs --tail 1 "${OS_DISTRIBUTION}-tester" | xargs)" 84 | - expected='success' 85 | - | 86 | echo "Response: ${actual}" 87 | if [[ "$actual" != "$expected" ]]; then 88 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 89 | exit -1 90 | fi 91 | finally: 92 | - | 93 | echo "---------Container Logs: ${OS_DISTRIBUTION}-app----------" 94 | echo 95 | docker logs "${OS_DISTRIBUTION}-app" || true 96 | echo 97 | echo "---------------------------------------------------" 98 | echo "--------Container Logs: ${OS_DISTRIBUTION}-tester--------" 99 | echo 100 | docker logs "${OS_DISTRIBUTION}-tester" || true 101 | echo 102 | echo "---------------------------------------------------" 103 | - echo "Cleaning up..." 104 | - docker stop "${OS_DISTRIBUTION}-app" || true 105 | - docker rm --force "${OS_DISTRIBUTION}-app" || true 106 | - docker stop "${OS_DISTRIBUTION}-tester" || true 107 | - docker rm --force "${OS_DISTRIBUTION}-tester" || true 108 | - docker network rm "${OS_DISTRIBUTION}-network" || true 109 | - docker rmi "${IMAGE_TAG}" || true 110 | -------------------------------------------------------------------------------- /test/integration/codebuild/buildspec.os.centos.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: centos 6 | NPX_BINARY_LOCATION: "/usr/bin/npx" 7 | batch: 8 | build-matrix: 9 | static: 10 | ignore-failure: false 11 | env: 12 | privileged-mode: true 13 | dynamic: 14 | env: 15 | variables: 16 | DISTRO_VERSION: 17 | - "stream9" 18 | RUNTIME_VERSION: 19 | - "18" 20 | - "20" 21 | phases: 22 | pre_build: 23 | commands: 24 | - export IMAGE_TAG="nodejs-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 25 | - echo "Extracting and including the Runtime Interface Emulator" 26 | - SCRATCH_DIR=".scratch" 27 | - mkdir "${SCRATCH_DIR}" 28 | - ARCHITECTURE=$(arch) 29 | - > 30 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 31 | RIE="aws-lambda-rie" 32 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 33 | RIE="aws-lambda-rie-arm64" 34 | else 35 | echo "Architecture $ARCHITECTURE is not currently supported." 36 | exit 1 37 | fi 38 | - tar -xvf test/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 39 | - > 40 | cp "test/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 41 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 42 | - > 43 | echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ 44 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 45 | - > 46 | if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; 47 | then 48 | echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." 49 | else 50 | echo "Performing DockerHub login . . ." 51 | docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD 52 | fi 53 | - echo "Building image ${IMAGE_TAG}" 54 | - > 55 | docker build . \ 56 | -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ 57 | -t "${IMAGE_TAG}" \ 58 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 59 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ 60 | --build-arg ARCHITECTURE="${ARCHITECTURE}" 61 | build: 62 | commands: 63 | - set -x 64 | - echo "Running Image ${IMAGE_TAG}" 65 | - docker network create "${OS_DISTRIBUTION}-network" 66 | - > 67 | docker run \ 68 | --detach \ 69 | --name "${OS_DISTRIBUTION}-app" \ 70 | --network "${OS_DISTRIBUTION}-network" \ 71 | --entrypoint="" \ 72 | "${IMAGE_TAG}" \ 73 | sh -c "/usr/bin/${RIE} ${NPX_BINARY_LOCATION} aws-lambda-ric index.handler" 74 | - sleep 2 75 | - > 76 | docker run \ 77 | --name "${OS_DISTRIBUTION}-tester" \ 78 | --env "TARGET=${OS_DISTRIBUTION}-app" \ 79 | --network "${OS_DISTRIBUTION}-network" \ 80 | --entrypoint="" \ 81 | "${IMAGE_TAG}" \ 82 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 83 | - actual="$(docker logs --tail 1 "${OS_DISTRIBUTION}-tester" | xargs)" 84 | - expected='success' 85 | - | 86 | echo "Response: ${actual}" 87 | if [[ "$actual" != "$expected" ]]; then 88 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 89 | exit -1 90 | fi 91 | finally: 92 | - | 93 | echo "---------Container Logs: ${OS_DISTRIBUTION}-app----------" 94 | echo 95 | docker logs "${OS_DISTRIBUTION}-app" || true 96 | echo 97 | echo "---------------------------------------------------" 98 | echo "--------Container Logs: ${OS_DISTRIBUTION}-tester--------" 99 | echo 100 | docker logs "${OS_DISTRIBUTION}-tester" || true 101 | echo 102 | echo "---------------------------------------------------" 103 | - echo "Cleaning up..." 104 | - docker stop "${OS_DISTRIBUTION}-app" || true 105 | - docker rm --force "${OS_DISTRIBUTION}-app" || true 106 | - docker stop "${OS_DISTRIBUTION}-tester" || true 107 | - docker rm --force "${OS_DISTRIBUTION}-tester" || true 108 | - docker network rm "${OS_DISTRIBUTION}-network" || true 109 | - docker rmi "${IMAGE_TAG}" || true 110 | -------------------------------------------------------------------------------- /test/integration/codebuild/buildspec.os.debian.1.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: debian 6 | NPX_BINARY_LOCATION: "/usr/local/bin/npx" 7 | batch: 8 | build-matrix: 9 | static: 10 | ignore-failure: false 11 | env: 12 | privileged-mode: true 13 | dynamic: 14 | env: 15 | variables: 16 | DISTRO_VERSION: 17 | - "buster" 18 | - "bullseye" 19 | RUNTIME_VERSION: 20 | - "18" 21 | - "20" 22 | phases: 23 | pre_build: 24 | commands: 25 | - export IMAGE_TAG="nodejs-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 26 | - echo "Extracting and including the Runtime Interface Emulator" 27 | - SCRATCH_DIR=".scratch" 28 | - mkdir "${SCRATCH_DIR}" 29 | - ARCHITECTURE=$(arch) 30 | - > 31 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 32 | RIE="aws-lambda-rie" 33 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 34 | RIE="aws-lambda-rie-arm64" 35 | else 36 | echo "Architecture $ARCHITECTURE is not currently supported." 37 | exit 1 38 | fi 39 | - tar -xvf test/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 40 | - > 41 | cp "test/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 42 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 43 | - > 44 | echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ 45 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 46 | - > 47 | if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; 48 | then 49 | echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." 50 | else 51 | echo "Performing DockerHub login . . ." 52 | docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD 53 | fi 54 | - > 55 | echo "RUN apt-get update && apt-get install -y curl" >> \ 56 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 57 | - echo "Building image ${IMAGE_TAG}" 58 | - > 59 | docker build . \ 60 | -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ 61 | -t "${IMAGE_TAG}" \ 62 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 63 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" 64 | build: 65 | commands: 66 | - set -x 67 | - echo "Running Image ${IMAGE_TAG}" 68 | - docker network create "${OS_DISTRIBUTION}-network" 69 | - > 70 | docker run \ 71 | --detach \ 72 | --name "${OS_DISTRIBUTION}-app" \ 73 | --network "${OS_DISTRIBUTION}-network" \ 74 | --entrypoint="" \ 75 | "${IMAGE_TAG}" \ 76 | sh -c "/usr/bin/${RIE} ${NPX_BINARY_LOCATION} aws-lambda-ric index.handler" 77 | - sleep 2 78 | - > 79 | docker run \ 80 | --name "${OS_DISTRIBUTION}-tester" \ 81 | --env "TARGET=${OS_DISTRIBUTION}-app" \ 82 | --network "${OS_DISTRIBUTION}-network" \ 83 | --entrypoint="" \ 84 | "${IMAGE_TAG}" \ 85 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 86 | - actual="$(docker logs --tail 1 "${OS_DISTRIBUTION}-tester" | xargs)" 87 | - expected='success' 88 | - | 89 | echo "Response: ${actual}" 90 | if [[ "$actual" != "$expected" ]]; then 91 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 92 | exit -1 93 | fi 94 | finally: 95 | - | 96 | echo "---------Container Logs: ${OS_DISTRIBUTION}-app----------" 97 | echo 98 | docker logs "${OS_DISTRIBUTION}-app" || true 99 | echo 100 | echo "---------------------------------------------------" 101 | echo "--------Container Logs: ${OS_DISTRIBUTION}-tester--------" 102 | echo 103 | docker logs "${OS_DISTRIBUTION}-tester" || true 104 | echo 105 | echo "---------------------------------------------------" 106 | - echo "Cleaning up..." 107 | - docker stop "${OS_DISTRIBUTION}-app" || true 108 | - docker rm --force "${OS_DISTRIBUTION}-app" || true 109 | - docker stop "${OS_DISTRIBUTION}-tester" || true 110 | - docker rm --force "${OS_DISTRIBUTION}-tester" || true 111 | - docker network rm "${OS_DISTRIBUTION}-network" || true 112 | - docker rmi "${IMAGE_TAG}" || true 113 | -------------------------------------------------------------------------------- /test/integration/codebuild/buildspec.os.debian.2.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: debian 6 | NPX_BINARY_LOCATION: "/usr/local/bin/npx" 7 | batch: 8 | build-matrix: 9 | static: 10 | ignore-failure: false 11 | env: 12 | privileged-mode: true 13 | dynamic: 14 | env: 15 | variables: 16 | DISTRO_VERSION: 17 | - "bookworm" 18 | RUNTIME_VERSION: 19 | - "18" 20 | - "20" 21 | phases: 22 | pre_build: 23 | commands: 24 | - export IMAGE_TAG="nodejs-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 25 | - echo "Extracting and including the Runtime Interface Emulator" 26 | - SCRATCH_DIR=".scratch" 27 | - mkdir "${SCRATCH_DIR}" 28 | - ARCHITECTURE=$(arch) 29 | - > 30 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 31 | RIE="aws-lambda-rie" 32 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 33 | RIE="aws-lambda-rie-arm64" 34 | else 35 | echo "Architecture $ARCHITECTURE is not currently supported." 36 | exit 1 37 | fi 38 | - tar -xvf test/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 39 | - > 40 | cp "test/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 41 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 42 | - > 43 | echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ 44 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 45 | - > 46 | if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; 47 | then 48 | echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." 49 | else 50 | echo "Performing DockerHub login . . ." 51 | docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD 52 | fi 53 | - > 54 | echo "RUN apt-get update && apt-get install -y curl" >> \ 55 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 56 | - echo "Building image ${IMAGE_TAG}" 57 | - > 58 | docker build . \ 59 | -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ 60 | -t "${IMAGE_TAG}" \ 61 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 62 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" 63 | build: 64 | commands: 65 | - set -x 66 | - echo "Running Image ${IMAGE_TAG}" 67 | - docker network create "${OS_DISTRIBUTION}-network" 68 | - > 69 | docker run \ 70 | --detach \ 71 | --name "${OS_DISTRIBUTION}-app" \ 72 | --network "${OS_DISTRIBUTION}-network" \ 73 | --entrypoint="" \ 74 | "${IMAGE_TAG}" \ 75 | sh -c "/usr/bin/${RIE} ${NPX_BINARY_LOCATION} aws-lambda-ric index.handler" 76 | - sleep 2 77 | - > 78 | docker run \ 79 | --name "${OS_DISTRIBUTION}-tester" \ 80 | --env "TARGET=${OS_DISTRIBUTION}-app" \ 81 | --network "${OS_DISTRIBUTION}-network" \ 82 | --entrypoint="" \ 83 | "${IMAGE_TAG}" \ 84 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 85 | - actual="$(docker logs --tail 1 "${OS_DISTRIBUTION}-tester" | xargs)" 86 | - expected='success' 87 | - | 88 | echo "Response: ${actual}" 89 | if [[ "$actual" != "$expected" ]]; then 90 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 91 | exit -1 92 | fi 93 | finally: 94 | - | 95 | echo "---------Container Logs: ${OS_DISTRIBUTION}-app----------" 96 | echo 97 | docker logs "${OS_DISTRIBUTION}-app" || true 98 | echo 99 | echo "---------------------------------------------------" 100 | echo "--------Container Logs: ${OS_DISTRIBUTION}-tester--------" 101 | echo 102 | docker logs "${OS_DISTRIBUTION}-tester" || true 103 | echo 104 | echo "---------------------------------------------------" 105 | - echo "Cleaning up..." 106 | - docker stop "${OS_DISTRIBUTION}-app" || true 107 | - docker rm --force "${OS_DISTRIBUTION}-app" || true 108 | - docker stop "${OS_DISTRIBUTION}-tester" || true 109 | - docker rm --force "${OS_DISTRIBUTION}-tester" || true 110 | - docker network rm "${OS_DISTRIBUTION}-network" || true 111 | - docker rmi "${IMAGE_TAG}" || true 112 | -------------------------------------------------------------------------------- /test/integration/codebuild/buildspec.os.ubuntu.1.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: ubuntu 6 | NPX_BINARY_LOCATION: "/usr/bin/npx" 7 | batch: 8 | build-matrix: 9 | static: 10 | ignore-failure: false 11 | env: 12 | privileged-mode: true 13 | dynamic: 14 | env: 15 | variables: 16 | DISTRO_VERSION: 17 | - "20.04" 18 | - "22.04" 19 | RUNTIME_VERSION: 20 | - "18" 21 | phases: 22 | pre_build: 23 | commands: 24 | - export IMAGE_TAG="nodejs-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 25 | - echo "Extracting and including the Runtime Interface Emulator" 26 | - SCRATCH_DIR=".scratch" 27 | - mkdir "${SCRATCH_DIR}" 28 | - ARCHITECTURE=$(arch) 29 | - > 30 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 31 | RIE="aws-lambda-rie" 32 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 33 | RIE="aws-lambda-rie-arm64" 34 | else 35 | echo "Architecture $ARCHITECTURE is not currently supported." 36 | exit 1 37 | fi 38 | - tar -xvf test/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 39 | - > 40 | cp "test/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 41 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 42 | - > 43 | echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ 44 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 45 | - > 46 | if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; 47 | then 48 | echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." 49 | else 50 | echo "Performing DockerHub login . . ." 51 | docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD 52 | fi 53 | - echo "Building image ${IMAGE_TAG}" 54 | - > 55 | docker build . \ 56 | -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ 57 | -t "${IMAGE_TAG}" \ 58 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 59 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ 60 | --build-arg ARCHITECTURE="${ARCHITECTURE}" 61 | build: 62 | commands: 63 | - set -x 64 | - echo "Running Image ${IMAGE_TAG}" 65 | - docker network create "${OS_DISTRIBUTION}-network" 66 | - > 67 | docker run \ 68 | --detach \ 69 | --name "${OS_DISTRIBUTION}-app" \ 70 | --network "${OS_DISTRIBUTION}-network" \ 71 | --entrypoint="" \ 72 | "${IMAGE_TAG}" \ 73 | sh -c "/usr/bin/${RIE} ${NPX_BINARY_LOCATION} aws-lambda-ric index.handler" 74 | - sleep 2 75 | - > 76 | docker run \ 77 | --name "${OS_DISTRIBUTION}-tester" \ 78 | --env "TARGET=${OS_DISTRIBUTION}-app" \ 79 | --network "${OS_DISTRIBUTION}-network" \ 80 | --entrypoint="" \ 81 | "${IMAGE_TAG}" \ 82 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 83 | - actual="$(docker logs --tail 1 "${OS_DISTRIBUTION}-tester" | xargs)" 84 | - expected='success' 85 | - | 86 | echo "Response: ${actual}" 87 | if [[ "$actual" != "$expected" ]]; then 88 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 89 | exit -1 90 | fi 91 | finally: 92 | - | 93 | echo "---------Container Logs: ${OS_DISTRIBUTION}-app----------" 94 | echo 95 | docker logs "${OS_DISTRIBUTION}-app" || true 96 | echo 97 | echo "---------------------------------------------------" 98 | echo "--------Container Logs: ${OS_DISTRIBUTION}-tester--------" 99 | echo 100 | docker logs "${OS_DISTRIBUTION}-tester" || true 101 | echo 102 | echo "---------------------------------------------------" 103 | - echo "Cleaning up..." 104 | - docker stop "${OS_DISTRIBUTION}-app" || true 105 | - docker rm --force "${OS_DISTRIBUTION}-app" || true 106 | - docker stop "${OS_DISTRIBUTION}-tester" || true 107 | - docker rm --force "${OS_DISTRIBUTION}-tester" || true 108 | - docker network rm "${OS_DISTRIBUTION}-network" || true 109 | - docker rmi "${IMAGE_TAG}" || true 110 | -------------------------------------------------------------------------------- /test/integration/codebuild/buildspec.os.ubuntu.2.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: ubuntu 6 | NPX_BINARY_LOCATION: "/usr/bin/npx" 7 | batch: 8 | build-matrix: 9 | static: 10 | ignore-failure: false 11 | env: 12 | privileged-mode: true 13 | dynamic: 14 | env: 15 | variables: 16 | DISTRO_VERSION: 17 | - "20.04" 18 | - "22.04" 19 | RUNTIME_VERSION: 20 | - "18" 21 | - "20" 22 | phases: 23 | pre_build: 24 | commands: 25 | - export IMAGE_TAG="nodejs-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 26 | - echo "Extracting and including the Runtime Interface Emulator" 27 | - SCRATCH_DIR=".scratch" 28 | - mkdir "${SCRATCH_DIR}" 29 | - ARCHITECTURE=$(arch) 30 | - > 31 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 32 | RIE="aws-lambda-rie" 33 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 34 | RIE="aws-lambda-rie-arm64" 35 | else 36 | echo "Architecture $ARCHITECTURE is not currently supported." 37 | exit 1 38 | fi 39 | - tar -xvf test/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 40 | - > 41 | cp "test/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 42 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 43 | - > 44 | echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ 45 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 46 | - > 47 | if [[ -z "${DOCKERHUB_USERNAME}" && -z "${DOCKERHUB_PASSWORD}" ]]; 48 | then 49 | echo "DockerHub credentials not set as CodeBuild environment variables. Continuing without docker login." 50 | else 51 | echo "Performing DockerHub login . . ." 52 | docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD 53 | fi 54 | - echo "Building image ${IMAGE_TAG}" 55 | - > 56 | docker build . \ 57 | -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ 58 | -t "${IMAGE_TAG}" \ 59 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 60 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ 61 | --build-arg ARCHITECTURE="${ARCHITECTURE}" 62 | build: 63 | commands: 64 | - set -x 65 | - echo "Running Image ${IMAGE_TAG}" 66 | - docker network create "${OS_DISTRIBUTION}-network" 67 | - > 68 | docker run \ 69 | --detach \ 70 | --name "${OS_DISTRIBUTION}-app" \ 71 | --network "${OS_DISTRIBUTION}-network" \ 72 | --entrypoint="" \ 73 | "${IMAGE_TAG}" \ 74 | sh -c "/usr/bin/${RIE} ${NPX_BINARY_LOCATION} aws-lambda-ric index.handler" 75 | - sleep 2 76 | - > 77 | docker run \ 78 | --name "${OS_DISTRIBUTION}-tester" \ 79 | --env "TARGET=${OS_DISTRIBUTION}-app" \ 80 | --network "${OS_DISTRIBUTION}-network" \ 81 | --entrypoint="" \ 82 | "${IMAGE_TAG}" \ 83 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 84 | - actual="$(docker logs --tail 1 "${OS_DISTRIBUTION}-tester" | xargs)" 85 | - expected='success' 86 | - | 87 | echo "Response: ${actual}" 88 | if [[ "$actual" != "$expected" ]]; then 89 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 90 | exit -1 91 | fi 92 | finally: 93 | - | 94 | echo "---------Container Logs: ${OS_DISTRIBUTION}-app----------" 95 | echo 96 | docker logs "${OS_DISTRIBUTION}-app" || true 97 | echo 98 | echo "---------------------------------------------------" 99 | echo "--------Container Logs: ${OS_DISTRIBUTION}-tester--------" 100 | echo 101 | docker logs "${OS_DISTRIBUTION}-tester" || true 102 | echo 103 | echo "---------------------------------------------------" 104 | - echo "Cleaning up..." 105 | - docker stop "${OS_DISTRIBUTION}-app" || true 106 | - docker rm --force "${OS_DISTRIBUTION}-app" || true 107 | - docker stop "${OS_DISTRIBUTION}-tester" || true 108 | - docker rm --force "${OS_DISTRIBUTION}-tester" || true 109 | - docker network rm "${OS_DISTRIBUTION}-network" || true 110 | - docker rmi "${IMAGE_TAG}" || true 111 | -------------------------------------------------------------------------------- /test/integration/docker/Dockerfile.echo.alpine: -------------------------------------------------------------------------------- 1 | # Define global args 2 | ARG FUNCTION_DIR="/home/app/" 3 | ARG RUNTIME_VERSION 4 | ARG DISTRO_VERSION 5 | 6 | # Stage 1 - build function and dependencies 7 | FROM node:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} AS build-image 8 | # Include global arg in this stage of the build 9 | ARG DISTRO_VERSION 10 | # Install aws-lambda-cpp build dependencies 11 | RUN apk add --update-cache \ 12 | build-base \ 13 | libtool \ 14 | musl-dev \ 15 | libressl-dev \ 16 | libffi-dev \ 17 | autoconf \ 18 | automake \ 19 | make \ 20 | cmake \ 21 | python3 \ 22 | libcurl 23 | 24 | # AWS Lambda CPP and libcurl rely on backtrace which requires libexecinfo from Alpine. 25 | # Since starting from Alpine3.17 libexecinfo is no longer available, temporarily source it from Alpine3.16 26 | # while awaiting an upstream resolution in AWS Lambda CPP. 27 | RUN if [[ "${DISTRO_VERSION}" == "3.17" ]] || [[ "${DISTRO_VERSION}" == "3.18" ]] ; \ 28 | then \ 29 | apk add --update-cache --repository=https://dl-cdn.alpinelinux.org/alpine/v3.16/main/ libexecinfo-dev ; \ 30 | else \ 31 | apk add --update-cache libexecinfo-dev ; \ 32 | fi 33 | 34 | # Include global arg in this stage of the build 35 | ARG FUNCTION_DIR 36 | # Create function directory 37 | RUN mkdir -p ${FUNCTION_DIR} 38 | 39 | # Copy & build Runtime Interface Client package (as we're installing it from a local filesystem source) 40 | WORKDIR ${FUNCTION_DIR}/deps/aws-lambda-ric 41 | COPY . . 42 | 43 | RUN make build && \ 44 | npm run test:unit 45 | 46 | # Copy function code 47 | COPY test/integration/test-handlers/echo/* ${FUNCTION_DIR} 48 | # Install the function's dependencies 49 | WORKDIR ${FUNCTION_DIR} 50 | RUN npm install 51 | 52 | 53 | # Stage 2 - final runtime image 54 | # Grab a fresh copy of the Node image 55 | FROM node:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} 56 | 57 | # Required for Node runtimes which use npm@8.6.0+ because 58 | # by default npm writes logs under /home/.npm and Lambda fs is read-only 59 | ENV NPM_CONFIG_CACHE=/tmp/.npm 60 | # Include global arg in this stage of the build 61 | ARG FUNCTION_DIR 62 | # Set working directory to function root directory 63 | WORKDIR ${FUNCTION_DIR} 64 | # Copy in the built dependencies 65 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 66 | 67 | ENTRYPOINT [ "/usr/local/bin/npx", "aws-lambda-ric" ] 68 | CMD [ "index.handler" ] 69 | -------------------------------------------------------------------------------- /test/integration/docker/Dockerfile.echo.amazonlinux: -------------------------------------------------------------------------------- 1 | # Define global args 2 | ARG FUNCTION_DIR="/home/app/" 3 | ARG RUNTIME_VERSION 4 | ARG DISTRO_VERSION 5 | 6 | # Stage 1 - bundle base image + runtime 7 | # Grab a fresh copy of the image and install Node 8 | FROM amazonlinux:${DISTRO_VERSION} AS node-amazonlinux 9 | # Include global arg in this stage of the build 10 | ARG RUNTIME_VERSION 11 | ARG DISTRO_VERSION 12 | # Install Py3 required to build Node16+ 13 | RUN if [[ "${DISTRO_VERSION}" == "2" ]] ; then amazon-linux-extras install python3.8 && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1 ; fi 14 | # Install NodeJS 15 | RUN if [[ "${RUNTIME_VERSION}" == "14" ]]; then \ 16 | yum install -y tar gzip xz && \ 17 | AARCH="$([[ "$(arch)" == "x86_64" ]] && echo "x64" || echo "arm64")" && \ 18 | NODE_URL="https://nodejs.org/download/release/v14.21.3/node-v14.21.3-linux-$AARCH.tar.xz" && \ 19 | curl -fL "$NODE_URL" | tar -C /usr --strip-components 1 -xJf - && \ 20 | yum clean all -q && rm -rf /var/cache/yum ; \ 21 | else \ 22 | yum install https://rpm.nodesource.com/pub_${RUNTIME_VERSION}.x/nodistro/repo/nodesource-release-nodistro-1.noarch.rpm -y && \ 23 | yum install nodejs -y --setopt=nodesource-nodejs.module_hotfixes=1s ; \ 24 | fi 25 | 26 | # Stage 2 - build function and dependencies 27 | FROM node-amazonlinux AS build-image 28 | ARG ARCHITECTURE 29 | # Install aws-lambda-cpp build dependencies 30 | RUN yum install -y \ 31 | tar \ 32 | gzip \ 33 | make \ 34 | wget \ 35 | autoconf \ 36 | automake \ 37 | libtool \ 38 | libcurl-devel \ 39 | gcc-c++ 40 | # Install a modern CMake 41 | RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0-linux-${ARCHITECTURE}.sh && \ 42 | sh cmake-install --skip-license --prefix=/usr --exclude-subdirectory; 43 | 44 | # Include global arg in this stage of the build 45 | ARG FUNCTION_DIR 46 | # Create function directory 47 | RUN mkdir -p ${FUNCTION_DIR} 48 | 49 | # Copy & build Runtime Interface Client package (as we're installing it from a local filesystem source) 50 | WORKDIR ${FUNCTION_DIR}/deps/aws-lambda-ric 51 | COPY . . 52 | 53 | # Node14 uses npm@6.14.18 by default which will downgrade permissions, if it's root, 54 | # before running any build scripts: https://github.com/npm/npm/issues/3497 55 | # Starting from npm@7.0.0, when npm is run as root, 56 | # scripts are always run with the effective uid and gid of the working directory owner. 57 | RUN if [[ $(node -v | cut -c 1-3) == "v14" ]] ; then npm install -g npm@7 ; fi 58 | RUN make build && \ 59 | npm run test:unit 60 | 61 | # Copy function code 62 | COPY test/integration/test-handlers/echo/* ${FUNCTION_DIR} 63 | # Install the function's dependencies 64 | WORKDIR ${FUNCTION_DIR} 65 | RUN npm install 66 | 67 | 68 | # Stage 3 - final runtime image 69 | # Grab a fresh copy of the Node image 70 | FROM node-amazonlinux 71 | 72 | # Required for Node runtimes which use npm@8.6.0+ because 73 | # by default npm writes logs under /home/.npm and Lambda fs is read-only 74 | ENV NPM_CONFIG_CACHE=/tmp/.npm 75 | # Include global arg in this stage of the build 76 | ARG FUNCTION_DIR 77 | # Set working directory to function root directory 78 | WORKDIR ${FUNCTION_DIR} 79 | # Copy in the built dependencies 80 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 81 | 82 | ENTRYPOINT [ "/usr/bin/npx", "aws-lambda-ric" ] 83 | CMD [ "index.handler" ] 84 | -------------------------------------------------------------------------------- /test/integration/docker/Dockerfile.echo.centos: -------------------------------------------------------------------------------- 1 | # Define global args 2 | ARG FUNCTION_DIR="/home/app/" 3 | ARG RUNTIME_VERSION 4 | ARG DISTRO_VERSION 5 | 6 | # Stage 1 - bundle base image + runtime 7 | # Grab a fresh copy of the image and install Node 8 | FROM quay.io/centos/centos:${DISTRO_VERSION} AS node-centos 9 | # Include global arg in this stage of the build 10 | ARG RUNTIME_VERSION 11 | # Install NodeJS 12 | RUN yum module enable -y nodejs:${RUNTIME_VERSION} && \ 13 | yum install -y nodejs 14 | 15 | 16 | # Stage 2 - build function and dependencies 17 | FROM node-centos AS build-image 18 | ARG ARCHITECTURE 19 | # Install aws-lambda-cpp build dependencies 20 | RUN yum install -y \ 21 | tar \ 22 | gzip \ 23 | make \ 24 | wget \ 25 | autoconf \ 26 | automake \ 27 | libtool \ 28 | libcurl-devel \ 29 | gcc-c++ 30 | # Install a modern CMake 31 | RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0-linux-${ARCHITECTURE}.sh && \ 32 | sh cmake-install --skip-license --prefix=/usr --exclude-subdirectory; 33 | 34 | # Include global arg in this stage of the build 35 | ARG FUNCTION_DIR 36 | # Create function directory 37 | RUN mkdir -p ${FUNCTION_DIR} 38 | 39 | # Copy & build Runtime Interface Client package (as we're installing it from a local filesystem source) 40 | WORKDIR ${FUNCTION_DIR}/deps/aws-lambda-ric 41 | COPY . . 42 | 43 | RUN make build && \ 44 | npm run test:unit 45 | 46 | # Copy function code 47 | COPY test/integration/test-handlers/echo/* ${FUNCTION_DIR} 48 | # Install the function's dependencies 49 | WORKDIR ${FUNCTION_DIR} 50 | RUN npm install 51 | 52 | 53 | # Stage 3 - final runtime image 54 | # Grab a fresh copy of the Node image 55 | FROM node-centos 56 | 57 | # Required for Node runtimes which use npm@8.6.0+ because 58 | # by default npm writes logs under /home/.npm and Lambda fs is read-only 59 | ENV NPM_CONFIG_CACHE=/tmp/.npm 60 | # Include global arg in this stage of the build 61 | ARG FUNCTION_DIR 62 | # Set working directory to function root directory 63 | WORKDIR ${FUNCTION_DIR} 64 | # Copy in the built dependencies 65 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 66 | 67 | ENTRYPOINT [ "/usr/bin/npx", "aws-lambda-ric" ] 68 | CMD [ "index.handler" ] 69 | -------------------------------------------------------------------------------- /test/integration/docker/Dockerfile.echo.debian: -------------------------------------------------------------------------------- 1 | # Define global args 2 | ARG FUNCTION_DIR="/home/app/" 3 | ARG RUNTIME_VERSION 4 | ARG DISTRO_VERSION 5 | 6 | # Stage 1 - build function and dependencies 7 | FROM node:${RUNTIME_VERSION}-${DISTRO_VERSION} AS build-image 8 | # Install aws-lambda-cpp build dependencies 9 | RUN apt-get update && \ 10 | apt-get install -y \ 11 | g++ \ 12 | make \ 13 | cmake \ 14 | libcurl4-openssl-dev 15 | 16 | # Include global arg in this stage of the build 17 | ARG FUNCTION_DIR 18 | # Create function directory 19 | RUN mkdir -p ${FUNCTION_DIR} 20 | 21 | # Copy & build Runtime Interface Client package (as we're installing it from a local filesystem source) 22 | WORKDIR ${FUNCTION_DIR}/deps/aws-lambda-ric 23 | COPY . . 24 | 25 | RUN make build && \ 26 | npm run test:unit 27 | 28 | # Copy function code 29 | COPY test/integration/test-handlers/echo/* ${FUNCTION_DIR} 30 | # Install the function's dependencies 31 | WORKDIR ${FUNCTION_DIR} 32 | RUN npm install 33 | 34 | 35 | # Stage 2 - final runtime image 36 | # Grab a fresh slim copy of the Node image 37 | FROM node:${RUNTIME_VERSION}-${DISTRO_VERSION}-slim 38 | 39 | # Required for Node runtimes which use npm@8.6.0+ because 40 | # by default npm writes logs under /home/.npm and Lambda fs is read-only 41 | ENV NPM_CONFIG_CACHE=/tmp/.npm 42 | # Include global arg in this stage of the build 43 | ARG FUNCTION_DIR 44 | # Set working directory to function root directory 45 | WORKDIR ${FUNCTION_DIR} 46 | # Copy in the built dependencies 47 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 48 | 49 | ENTRYPOINT [ "/usr/local/bin/npx", "aws-lambda-ric" ] 50 | CMD [ "index.handler" ] 51 | -------------------------------------------------------------------------------- /test/integration/docker/Dockerfile.echo.ubuntu: -------------------------------------------------------------------------------- 1 | # Define global args 2 | ARG FUNCTION_DIR="/home/app/" 3 | ARG RUNTIME_VERSION 4 | ARG DISTRO_VERSION 5 | 6 | # Stage 1 - bundle base image + runtime 7 | # Grab a fresh copy of the image and install Node 8 | FROM ubuntu:${DISTRO_VERSION} AS node-ubuntu 9 | # Non-interactive mode 10 | ENV DEBIAN_FRONTEND=noninteractive 11 | # Install NodeJS 12 | ARG RUNTIME_VERSION 13 | RUN apt-get update && \ 14 | apt-get install -y \ 15 | curl && \ 16 | curl -sL https://deb.nodesource.com/setup_${RUNTIME_VERSION}.x | bash - && \ 17 | apt-get install -y nodejs 18 | 19 | 20 | # Stage 2 - build function and dependencies 21 | FROM node-ubuntu AS build-image 22 | ARG ARCHITECTURE 23 | # Install aws-lambda-cpp build dependencies 24 | RUN apt-get update && \ 25 | apt-get install -y \ 26 | g++ \ 27 | gcc \ 28 | tar \ 29 | curl \ 30 | gzip \ 31 | make \ 32 | cmake \ 33 | autoconf \ 34 | automake \ 35 | libtool \ 36 | wget \ 37 | libcurl4-openssl-dev \ 38 | python3 39 | # Install a modern CMake 40 | RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0-linux-${ARCHITECTURE}.sh && \ 41 | sh cmake-install --skip-license --prefix=/usr --exclude-subdirectory; 42 | 43 | # Include global arg in this stage of the build 44 | ARG FUNCTION_DIR 45 | # Create function directory 46 | RUN mkdir -p ${FUNCTION_DIR} 47 | 48 | # Copy & build Runtime Interface Client package (as we're installing it from a local filesystem source) 49 | WORKDIR ${FUNCTION_DIR}/deps/aws-lambda-ric 50 | COPY . . 51 | 52 | RUN make build && \ 53 | npm run test:unit 54 | 55 | # Copy function code 56 | COPY test/integration/test-handlers/echo/* ${FUNCTION_DIR} 57 | # Install the function's dependencies 58 | WORKDIR ${FUNCTION_DIR} 59 | RUN npm install 60 | 61 | 62 | # Stage 3 - final runtime image 63 | # Grab a fresh copy of the Node image 64 | FROM node-ubuntu 65 | 66 | # Required for Node runtimes which use npm@8.6.0+ because 67 | # by default npm writes logs under /home/.npm and Lambda fs is read-only 68 | ENV NPM_CONFIG_CACHE=/tmp/.npm 69 | # Include global arg in this stage of the build 70 | ARG FUNCTION_DIR 71 | # Set working directory to function root directory 72 | WORKDIR ${FUNCTION_DIR} 73 | # Copy in the built dependencies 74 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 75 | 76 | ENTRYPOINT [ "/usr/bin/npx", "aws-lambda-ric" ] 77 | CMD [ "index.handler" ] 78 | -------------------------------------------------------------------------------- /test/integration/docker/Dockerfile.programmatic.alpine: -------------------------------------------------------------------------------- 1 | # Define global args 2 | ARG FUNCTION_DIR="/home/app/" 3 | ARG RUNTIME_VERSION 4 | ARG DISTRO_VERSION 5 | 6 | # Stage 1 - build function and dependencies 7 | FROM node:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} AS build-image 8 | # Include global arg in this stage of the build 9 | ARG DISTRO_VERSION 10 | # Install aws-lambda-cpp build dependencies 11 | RUN apk add --update-cache \ 12 | build-base \ 13 | libtool \ 14 | musl-dev \ 15 | libressl-dev \ 16 | libffi-dev \ 17 | autoconf \ 18 | automake \ 19 | make \ 20 | cmake \ 21 | python3 \ 22 | libcurl 23 | 24 | # AWS Lambda CPP and libcurl rely on backtrace which requires libexecinfo from Alpine. 25 | # Since starting from Alpine3.17 libexecinfo is no longer available, temporarily source it from Alpine3.16 26 | # while awaiting an upstream resolution in AWS Lambda CPP. 27 | RUN if [[ "${DISTRO_VERSION}" == "3.17" ]] || [[ "${DISTRO_VERSION}" == "3.18" ]] ; \ 28 | then \ 29 | apk add --update-cache --repository=https://dl-cdn.alpinelinux.org/alpine/v3.16/main/ libexecinfo-dev ; \ 30 | else \ 31 | apk add --update-cache libexecinfo-dev ; \ 32 | fi 33 | 34 | # Include global arg in this stage of the build 35 | ARG FUNCTION_DIR 36 | # Create function directory 37 | RUN mkdir -p ${FUNCTION_DIR} 38 | 39 | # Copy & build Runtime Interface Client package (as we're installing it from a local filesystem source) 40 | WORKDIR ${FUNCTION_DIR}/deps/aws-lambda-ric 41 | COPY . . 42 | 43 | RUN make build && \ 44 | npm run test:unit 45 | 46 | # Copy function code 47 | COPY test/integration/test-handlers/programmatic/* ${FUNCTION_DIR} 48 | # Install the function's dependencies 49 | WORKDIR ${FUNCTION_DIR} 50 | RUN npm install 51 | 52 | 53 | # Stage 2 - final runtime image 54 | # Grab a fresh copy of the Node image 55 | FROM node:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} 56 | 57 | # Required for Node runtimes which use npm@8.6.0+ because 58 | # by default npm writes logs under /home/.npm and Lambda fs is read-only 59 | ENV NPM_CONFIG_CACHE=/tmp/.npm 60 | # Include global arg in this stage of the build 61 | ARG FUNCTION_DIR 62 | # Set working directory to function root directory 63 | WORKDIR ${FUNCTION_DIR} 64 | # Copy in the built dependencies 65 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 66 | 67 | CMD [ "/usr/local/bin/node", "index.mjs" ] 68 | -------------------------------------------------------------------------------- /test/integration/resources/aws-lambda-rie-arm64.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-lambda-nodejs-runtime-interface-client/a5ae1c2a92708e81c9df4949c60fd9e1e6e46bed/test/integration/resources/aws-lambda-rie-arm64.tar.gz -------------------------------------------------------------------------------- /test/integration/resources/aws-lambda-rie.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-lambda-nodejs-runtime-interface-client/a5ae1c2a92708e81c9df4949c60fd9e1e6e46bed/test/integration/resources/aws-lambda-rie.tar.gz -------------------------------------------------------------------------------- /test/integration/test-handlers/echo/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.handler = async (event, context) => { 4 | console.log('hello world'); 5 | return 'success'; 6 | }; 7 | -------------------------------------------------------------------------------- /test/integration/test-handlers/echo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "echo-hanlder", 3 | "version": "1.0.0", 4 | "description": "Sample Lambda echo handler for NodeJS", 5 | "author": "AWS Lambda", 6 | "license": "Apache-2.0", 7 | "dependencies": { 8 | "aws-lambda-ric": "file:deps/aws-lambda-ric" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/integration/test-handlers/programmatic/index.mjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { run } from 'aws-lambda-ric'; 4 | 5 | const echo = async (event, context) => { 6 | console.log('hello world'); 7 | return 'success'; 8 | }; 9 | 10 | await run(echo); 11 | -------------------------------------------------------------------------------- /test/integration/test-handlers/programmatic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "programmatic-hanlder", 3 | "version": "1.0.0", 4 | "description": "Sample Lambda echo handler for NodeJS", 5 | "author": "AWS Lambda", 6 | "license": "Apache-2.0", 7 | "dependencies": { 8 | "aws-lambda-ric": "file:deps/aws-lambda-ric" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/unit/Dockerfile.nodejs18.x: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | RUN apt-get update 3 | RUN apt-get install -y cmake 4 | WORKDIR /tmp 5 | COPY . /tmp 6 | RUN npm install --ci 7 | CMD ["npm", "run", "test"] 8 | -------------------------------------------------------------------------------- /test/unit/Dockerfile.nodejs20.x: -------------------------------------------------------------------------------- 1 | FROM node:20 2 | RUN apt-get update 3 | RUN apt-get install -y cmake 4 | WORKDIR /tmp 5 | COPY . /tmp 6 | RUN npm install --ci 7 | CMD ["npm", "run", "test"] 8 | -------------------------------------------------------------------------------- /test/unit/Dockerfile.nodejs22.x: -------------------------------------------------------------------------------- 1 | FROM node:22 2 | RUN apt-get update 3 | RUN apt-get install -y cmake 4 | WORKDIR /tmp 5 | COPY . /tmp 6 | RUN npm install --ci 7 | CMD ["npm", "run", "test"] 8 | -------------------------------------------------------------------------------- /test/unit/ErrorsTest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | require('should'); 8 | let Errors = require('lambda-runtime/Errors'); 9 | 10 | describe('Formatted Error Logging', () => { 11 | it('should fall back to a minimal error format when an exception occurs', () => { 12 | let error = new Error('custom message'); 13 | error.name = 'CircularError'; 14 | error.backlink = error; 15 | 16 | let loggedError = JSON.parse(Errors.toFormatted(error).trim()); 17 | loggedError.should.have.property('errorType', 'CircularError'); 18 | loggedError.should.have.property('errorMessage', 'custom message'); 19 | loggedError.should.have.property('trace').with.length(11); 20 | }); 21 | }); 22 | 23 | describe('Invalid chars in HTTP header', () => { 24 | it('should be replaced', () => { 25 | let errorWithInvalidChar = new Error('\x7F \x7F'); 26 | errorWithInvalidChar.name = 'ErrorWithInvalidChar'; 27 | 28 | let loggedError = Errors.toRapidResponse(errorWithInvalidChar); 29 | loggedError.should.have.property('errorType', 'ErrorWithInvalidChar'); 30 | loggedError.should.have.property('errorMessage', '%7F %7F'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/unit/FakeTelemetryTarget.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const os = require('os'); 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | const assert = require('assert'); 11 | 12 | const levels = Object.freeze({ 13 | TRACE: { name: 'TRACE', tlvMask: 0b00100 }, 14 | DEBUG: { name: 'DEBUG', tlvMask: 0b01000 }, 15 | INFO: { name: 'INFO', tlvMask: 0b01100 }, 16 | WARN: { name: 'WARN', tlvMask: 0b10000 }, 17 | ERROR: { name: 'ERROR', tlvMask: 0b10100 }, 18 | FATAL: { name: 'FATAL', tlvMask: 0b11000 }, 19 | }); 20 | 21 | const TextName = 'TEXT'; 22 | 23 | /** 24 | * A fake implementation of the multilne logging protocol. 25 | * Read and write log frames to a temp file and provide an asserting helper for 26 | * reading individual log statements from the file. 27 | */ 28 | module.exports = class FakeTelemetryTarget { 29 | constructor() { 30 | this.readTarget = 0; 31 | this.writeTarget = 0; 32 | } 33 | 34 | openFile() { 35 | let tempTelemetryDir = fs.mkdtempSync( 36 | path.join(os.tmpdir(), 'AWSLambdaNodeJsTelemetry-'), 37 | ); 38 | this.writeTarget = fs.openSync(path.join(tempTelemetryDir, 'log'), 'as+'); 39 | this.readTarget = fs.openSync(path.join(tempTelemetryDir, 'log'), 'rs+'); 40 | console.log( 41 | 'Generate new telemetry file', 42 | tempTelemetryDir, 43 | 'with file descriptor', 44 | this.readTarget, 45 | ); 46 | } 47 | 48 | closeFile() { 49 | console.log(`Close telemetry filedescriptor ${this.readTarget}`); 50 | fs.closeSync(this.readTarget); 51 | fs.closeSync(this.writeTarget); 52 | this.readTarget = 0; 53 | this.writeTarget = 0; 54 | } 55 | 56 | updateEnv() { 57 | process.env['_LAMBDA_TELEMETRY_LOG_FD'] = this.writeTarget; 58 | } 59 | 60 | /** 61 | * Read a single line from the telemetry file. 62 | * Explodes when: 63 | * - no line is present 64 | * - the prefix is malformed 65 | * - there aren't enough bytes 66 | */ 67 | readLine(level = 'INFO', format = TextName, expectEmpty = false) { 68 | let readLength = () => { 69 | let logPrefix = Buffer.alloc(16); 70 | let actualReadBytes = fs.readSync( 71 | this.readTarget, 72 | logPrefix, 73 | 0, 74 | logPrefix.length, 75 | ); 76 | 77 | if (expectEmpty) { 78 | assert.strictEqual( 79 | actualReadBytes, 80 | 0, 81 | `Expected actualReadBytes[${actualReadBytes}] = 0`, 82 | ); 83 | return 0; 84 | } 85 | 86 | assert.strictEqual( 87 | actualReadBytes, 88 | logPrefix.length, 89 | `Expected actualReadBytes[${actualReadBytes}] = ${logPrefix.length}`, 90 | ); 91 | 92 | var _tlvHeader; 93 | if (format === TextName) 94 | _tlvHeader = (0xa55a0003 | levels[level].tlvMask) >>> 0; 95 | else _tlvHeader = (0xa55a0002 | levels[level].tlvMask) >>> 0; 96 | 97 | let _logIdentifier = Buffer.from(_tlvHeader.toString(16), 'hex'); 98 | assert.strictEqual( 99 | logPrefix.lastIndexOf(_logIdentifier), 100 | 0, 101 | `log prefix ${logPrefix.toString( 102 | 'hex', 103 | )} should start with ${_logIdentifier.toString('hex')}`, 104 | ); 105 | let len = logPrefix.readUInt32BE(4); 106 | // discard the timestamp 107 | logPrefix.readBigUInt64BE(8); 108 | return len; 109 | }; 110 | 111 | let lineLength = readLength(); 112 | if (lineLength === 0) { 113 | return ''; 114 | } 115 | 116 | let lineBytes = Buffer.alloc(lineLength); 117 | let actualLineSize = fs.readSync( 118 | this.readTarget, 119 | lineBytes, 120 | 0, 121 | lineBytes.length, 122 | ); 123 | assert.strictEqual( 124 | actualLineSize, 125 | lineBytes.length, 126 | 'The log line must match the length specified in the frame header', 127 | ); 128 | return lineBytes.toString('utf8'); 129 | } 130 | }; 131 | -------------------------------------------------------------------------------- /test/unit/InvokeContextTest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | require('should'); 8 | const sleep = require('util').promisify(setTimeout); 9 | 10 | let InvokeContext = require('lambda-runtime/InvokeContext'); 11 | 12 | describe('Getting remaining invoke time', () => { 13 | it('should reduce by at least elapsed time', async () => { 14 | let ctx = new InvokeContext({ 15 | 'lambda-runtime-deadline-ms': Date.now() + 1000, 16 | }); 17 | 18 | const timeout = 100; 19 | let before = ctx._headerData().getRemainingTimeInMillis(); 20 | await sleep(timeout + 10); 21 | let after = ctx._headerData().getRemainingTimeInMillis(); 22 | (before - after).should.greaterThanOrEqual( 23 | timeout - 1 /* Timers are not precise, allow 1 ms drift */, 24 | ); 25 | }); 26 | 27 | it('should be within range', () => { 28 | let ctx = new InvokeContext({ 29 | 'lambda-runtime-deadline-ms': Date.now() + 1000, 30 | }); 31 | 32 | let remainingTime = ctx._headerData().getRemainingTimeInMillis(); 33 | 34 | remainingTime.should.greaterThan(0); 35 | remainingTime.should.lessThanOrEqual(1000); 36 | }); 37 | }); 38 | 39 | describe('Verifying tenant id', () => { 40 | it('should return undefined if tenant id is not set', () => { 41 | let ctx = new InvokeContext({}); 42 | 43 | (ctx._headerData().tenantId === undefined).should.be.true(); 44 | }); 45 | it('should return undefined if tenant id is set to undefined', () => { 46 | let ctx = new InvokeContext({ 47 | 'lambda-runtime-aws-tenant-id': undefined, 48 | }); 49 | 50 | (ctx._headerData().tenantId === undefined).should.be.true(); 51 | }); 52 | it('should return empty if tenant id is set to empty string', () => { 53 | let ctx = new InvokeContext({ 54 | 'lambda-runtime-aws-tenant-id': '', 55 | }); 56 | 57 | (ctx._headerData().tenantId === '').should.be.true(); 58 | }); 59 | it('should return the same id if a valid tenant id is set', () => { 60 | let ctx = new InvokeContext({ 61 | 'lambda-runtime-aws-tenant-id': 'blue', 62 | }); 63 | 64 | ctx._headerData().tenantId.should.equal('blue'); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/unit/LoggingGlobals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | /** 8 | * Testing logging in unit tests requires manipulating the global console and 9 | * stdout objects. 10 | * This module provides methods for safely capturing and restoring these 11 | * objects under test. 12 | */ 13 | 14 | const levels = Object.freeze({ 15 | TRACE: { name: 'TRACE' }, 16 | DEBUG: { name: 'DEBUG' }, 17 | INFO: { name: 'INFO' }, 18 | WARN: { name: 'WARN' }, 19 | ERROR: { name: 'ERROR' }, 20 | FATAL: { name: 'FATAL' }, 21 | }); 22 | 23 | const formats = Object.freeze({ 24 | TEXT: { name: 'TEXT' }, 25 | JSON: { name: 'JSON' }, 26 | }); 27 | 28 | module.exports.consoleSnapshot = () => { 29 | let log = console.log; 30 | let debug = console.debug; 31 | let info = console.info; 32 | let warn = console.warn; 33 | let error = console.error; 34 | let trace = console.trace; 35 | let fatal = console.fatal; 36 | 37 | return function restoreConsole() { 38 | console.log = log; 39 | console.debug = debug; 40 | console.info = info; 41 | console.warn = warn; 42 | console.error = error; 43 | console.trace = trace; 44 | console.fatal = fatal; 45 | }; 46 | }; 47 | 48 | /** 49 | * Capture all of the writes to a given stream. 50 | */ 51 | module.exports.captureStream = function captureStream(stream) { 52 | let originalWrite = stream.write; 53 | let buf = ''; 54 | return { 55 | hook: () => { 56 | buf = ''; // reset the buffer 57 | stream.write = function (chunk, _encoding, _callback) { 58 | buf += chunk.toString(); 59 | originalWrite.apply(stream, arguments); 60 | }; 61 | }, 62 | unhook: () => (stream.write = originalWrite), 63 | captured: () => buf, 64 | resetBuffer: () => (buf = ''), 65 | }; 66 | }; 67 | 68 | module.exports.loggingConfig = class loggingConfig { 69 | turnOnStructuredLogging() { 70 | process.env['AWS_LAMBDA_LOG_FORMAT'] = formats.JSON.name; 71 | } 72 | 73 | turnOffStructuredLogging() { 74 | delete process.env['AWS_LAMBDA_LOG_FORMAT']; 75 | } 76 | 77 | setLogLevel(level) { 78 | if (levels[level] === undefined) { 79 | return; 80 | } 81 | process.env['AWS_LAMBDA_LOG_LEVEL'] = levels[level].name; 82 | } 83 | 84 | resetLogLevel() { 85 | delete process.env['AWS_LAMBDA_LOG_LEVEL']; 86 | } 87 | }; 88 | -------------------------------------------------------------------------------- /test/unit/RAPIDClientTest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | require('should'); 8 | 9 | let RAPIDClient = require('lambda-runtime/RAPIDClient.js'); 10 | let runtimeErrors = require('lambda-runtime/Errors.js'); 11 | 12 | /** 13 | * Stub request object. 14 | * Provides no-op definitions of the request functions used by the rapid client. 15 | */ 16 | const noOpRequest = Object.freeze({ 17 | /* no op, return itself to allow continuations/chaining */ 18 | on: () => noOpRequest, 19 | /* no op, return itself to allow continuations/chaining */ 20 | end: () => noOpRequest, 21 | }); 22 | 23 | class StubHttp { 24 | constructor() { 25 | this.lastUsedOptions = {}; 26 | this.Agent = class FakeAgent {}; 27 | } 28 | 29 | request(options, _callback) { 30 | this.lastUsedOptions = options; 31 | return noOpRequest; 32 | } 33 | } 34 | 35 | class NoOpNativeHttp { 36 | constructor() { 37 | this.lastRequestId = ''; 38 | this.lastErrorRequestId = ''; 39 | } 40 | 41 | done(requestId) { 42 | this.lastRequestId = requestId; 43 | } 44 | 45 | error(requestId) { 46 | this.lastErrorRequestId = requestId; 47 | } 48 | } 49 | 50 | class MockNativeClient { 51 | constructor(response) { 52 | this.response = response; 53 | this.called = false; 54 | this.shouldThrowError = false; 55 | } 56 | 57 | next() { 58 | this.called = true; 59 | if (this.shouldThrowError) { 60 | return Promise.reject(new Error('Failed to get next invocation')); 61 | } else { 62 | return Promise.resolve(this.response); 63 | } 64 | } 65 | } 66 | 67 | class EvilError extends Error { 68 | get name() { 69 | throw 'gotcha'; 70 | } 71 | } 72 | 73 | const EXPECTED_ERROR_HEADER = 'Lambda-Runtime-Function-Error-Type'; 74 | 75 | describe('building error requests with the RAPIDClient', () => { 76 | let stubHttp = new StubHttp(); 77 | let client = new RAPIDClient('notUsed:1337', stubHttp, new NoOpNativeHttp()); 78 | 79 | let errors = [ 80 | [new Error('generic failure'), 'Error'], 81 | [new runtimeErrors.ImportModuleError(), 'Runtime.ImportModuleError'], 82 | [new runtimeErrors.HandlerNotFound(), 'Runtime.HandlerNotFound'], 83 | [new runtimeErrors.MalformedHandlerName(), 'Runtime.MalformedHandlerName'], 84 | [new runtimeErrors.UserCodeSyntaxError(), 'Runtime.UserCodeSyntaxError'], 85 | [{ data: 'some random object' }, 'object'], 86 | [new EvilError(), 'handled'], 87 | ]; 88 | 89 | describe('the error header in postInitError', () => { 90 | errors.forEach(([error, name]) => { 91 | it(`should be ${name} for ${error.constructor.name}`, () => { 92 | client.postInitError(error); 93 | stubHttp.lastUsedOptions.should.have 94 | .property('headers') 95 | .have.property(EXPECTED_ERROR_HEADER, name); 96 | }); 97 | }); 98 | }); 99 | }); 100 | 101 | describe('invalid request id works', () => { 102 | const nativeClient = new NoOpNativeHttp(); 103 | const client = new RAPIDClient('notUsed:1337', undefined, nativeClient); 104 | 105 | [ 106 | // Encoding expected: 107 | ['#', '%23'], 108 | ['%', '%25'], 109 | ['/', '%2F'], 110 | ['?', '%3F'], 111 | ['\x7F', '%7F'], 112 | ["", "%3Cscript%3Ealert('1')%3C%2Fscript%3E"], 113 | ['⚡', '%E2%9A%A1'], 114 | 115 | // No encoding: 116 | ['.', '.'], 117 | ['..', '..'], 118 | ['a', 'a'], 119 | [ 120 | '59b22c65-fa81-47fb-a6dc-23028a63566f', 121 | '59b22c65-fa81-47fb-a6dc-23028a63566f', 122 | ], 123 | ].forEach(([requestId, expected]) => { 124 | it(`postInvocationResponse should encode requestId: '${requestId}'`, () => { 125 | client.postInvocationResponse({}, requestId, () => {}); 126 | nativeClient.lastRequestId.should.be.equal(expected); 127 | }); 128 | 129 | it(`postInvocationError should encode requestId: '${requestId}'`, () => { 130 | client.postInvocationError(new Error(), requestId, () => {}); 131 | nativeClient.lastErrorRequestId.should.be.equal(expected); 132 | }); 133 | }); 134 | }); 135 | 136 | describe('next invocation with native client works', () => { 137 | it('should call the native client next() method', async () => { 138 | const mockNative = new MockNativeClient({ 139 | bodyJson: '', 140 | headers: { 141 | 'lambda-runtime-aws-request-id': 'test-request-id', 142 | }, 143 | }); 144 | const client = new RAPIDClient('notUsed:1337', undefined, mockNative); 145 | client.useAlternativeClient = false; 146 | 147 | await client.nextInvocation(); 148 | // verify native client was called 149 | mockNative.called.should.be.true(); 150 | }); 151 | it('should parse all required headers', async () => { 152 | const mockResponse = { 153 | bodyJson: '{"message":"Hello from Lambda!"}', 154 | headers: { 155 | 'lambda-runtime-aws-request-id': 'test-request-id', 156 | 'lambda-runtime-deadline-ms': 1619712000000, 157 | 'lambda-runtime-trace-id': 'test-trace-id', 158 | 'lambda-runtime-invoked-function-arn': 'test-function-arn', 159 | 'lambda-runtime-client-context': '{"client":{"app_title":"MyApp"}}', 160 | 'lambda-runtime-cognito-identity': 161 | '{"identityId":"id123","identityPoolId":"pool123"}', 162 | 'lambda-runtime-aws-tenant-id': 'test-tenant-id', 163 | }, 164 | }; 165 | 166 | const mockNative = new MockNativeClient(mockResponse); 167 | const client = new RAPIDClient('notUsed:1337', undefined, mockNative); 168 | 169 | client.useAlternativeClient = false; 170 | const response = await client.nextInvocation(); 171 | 172 | // Verify all headers are present 173 | response.headers.should.have.property( 174 | 'lambda-runtime-aws-request-id', 175 | 'test-request-id', 176 | ); 177 | response.headers.should.have.property( 178 | 'lambda-runtime-deadline-ms', 179 | 1619712000000, 180 | ); 181 | response.headers.should.have.property( 182 | 'lambda-runtime-trace-id', 183 | 'test-trace-id', 184 | ); 185 | response.headers.should.have.property( 186 | 'lambda-runtime-invoked-function-arn', 187 | 'test-function-arn', 188 | ); 189 | response.headers.should.have.property( 190 | 'lambda-runtime-client-context', 191 | '{"client":{"app_title":"MyApp"}}', 192 | ); 193 | response.headers.should.have.property( 194 | 'lambda-runtime-cognito-identity', 195 | '{"identityId":"id123","identityPoolId":"pool123"}', 196 | ); 197 | response.headers.should.have.property( 198 | 'lambda-runtime-aws-tenant-id', 199 | 'test-tenant-id', 200 | ); 201 | // Verify body is correctly passed through 202 | response.bodyJson.should.equal('{"message":"Hello from Lambda!"}'); 203 | }); 204 | it('should handle native client errors', async () => { 205 | const nativeClient = new MockNativeClient({}); 206 | nativeClient.shouldThrowError = true; 207 | 208 | const client = new RAPIDClient('localhost:8080', null, nativeClient); 209 | client.useAlternativeClient = false; 210 | 211 | await client.nextInvocation().should.be.rejectedWith('Failed to get next invocation'); 212 | }); 213 | }); 214 | -------------------------------------------------------------------------------- /test/unit/StreamingContextTest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | require('should'); 8 | const StreamingContext = require('lambda-runtime/StreamingContext.js'); 9 | const { PassThrough } = require('stream'); 10 | const BeforeExitListener = require('lambda-runtime/BeforeExitListener.js'); 11 | 12 | class MockRapidClient { 13 | constructor() { 14 | this.sentErrors = []; 15 | this.responseStream = new PassThrough(); 16 | this.responseStream.fail = (err, callback) => { 17 | this.sentErrors.push({ err }); 18 | callback(); 19 | }; 20 | } 21 | 22 | getStreamForInvocationResponse() { 23 | return { request: this.responseStream, responseDone: undefined }; 24 | } 25 | } 26 | 27 | describe('StreamingContext', () => { 28 | it('can set callbackWaitsForEmptyEventLoop', () => { 29 | const ctx = StreamingContext.build(); 30 | 31 | ctx.callbackWaitsForEmptyEventLoop = true; 32 | ctx.callbackWaitsForEmptyEventLoop.should.be.equal(true); 33 | 34 | ctx.callbackWaitsForEmptyEventLoop = false; 35 | ctx.callbackWaitsForEmptyEventLoop.should.be.equal(false); 36 | }); 37 | 38 | it('can create stream', () => { 39 | const id = '12-3-4-56'; 40 | const client = new MockRapidClient(); 41 | const ctx = StreamingContext.build( 42 | client, 43 | id, 44 | () => {}, 45 | JSON.stringify({}), 46 | ); 47 | const stream = ctx.createStream(); 48 | should(stream).not.be.empty(); 49 | }); 50 | 51 | it('cannot create stream more than once', () => { 52 | const id = '12-3-4-56'; 53 | const client = new MockRapidClient(); 54 | const ctx = StreamingContext.build( 55 | client, 56 | id, 57 | () => {}, 58 | JSON.stringify({}), 59 | ); 60 | const stream = ctx.createStream(); 61 | should(stream).not.be.empty(); 62 | 63 | for (let i = 0; i < 5; i++) { 64 | should(() => ctx.createStream()).throw({ 65 | message: 66 | 'Cannot create stream for the same StreamingContext more than once.', 67 | }); 68 | } 69 | }); 70 | 71 | [true, false].forEach((callbackWaitsForEmptyEventLoop) => 72 | [ 73 | { 74 | error: new Error('too much sun'), 75 | expected: 'too much sun', 76 | }, 77 | { 78 | error: 'too much sun', 79 | expected: 'too much sun', 80 | }, 81 | ].forEach((v) => 82 | it(`can call next after fail (callbackWaitsForEmptyEventLoop: ${callbackWaitsForEmptyEventLoop}, error: ${typeof v.error})`, () => { 83 | // This test will print "Invoke Error" to stderr which is to be expected. 84 | 85 | let nextCalled = 0; 86 | const ID = '12-3-4-56'; 87 | const client = new MockRapidClient(); 88 | const ctx = StreamingContext.build( 89 | client, 90 | ID, 91 | () => nextCalled++, 92 | JSON.stringify({}), 93 | ); 94 | ctx.callbackWaitsForEmptyEventLoop = callbackWaitsForEmptyEventLoop; 95 | const { scheduleNext, fail } = ctx.createStream(); 96 | 97 | fail(v.error, scheduleNext); 98 | client.responseStream.fail(v.error, scheduleNext); 99 | 100 | const verify = () => { 101 | nextCalled.should.be.equal(1); 102 | 103 | console.log('client.sentErrors', client.sentErrors); 104 | console.log('client.invocationErrors', client.invocationErrors); 105 | 106 | client.sentErrors.length.should.be.equal(1); 107 | if (typeof v.error === 'string') { 108 | client.sentErrors[0].err.should.be.equal(v.expected); 109 | } else { 110 | client.sentErrors[0].err.message.should.be.equal(v.expected); 111 | } 112 | }; 113 | 114 | if (v) { 115 | BeforeExitListener.invoke(); 116 | setImmediate(() => verify()); 117 | } else { 118 | verify(); 119 | } 120 | }), 121 | ), 122 | ); 123 | }); 124 | -------------------------------------------------------------------------------- /test/unit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_unit", 3 | "version": "1.0.0", 4 | "author": "AWS Lambda", 5 | "license": "Apache-2.0", 6 | "eslintConfig": { 7 | "extends": [ 8 | "plugin:prettier/recommended" 9 | ], 10 | "env": { 11 | "node": true, 12 | "mocha": true, 13 | "es6": true 14 | }, 15 | "parserOptions": { 16 | "ecmaVersion": 2020 17 | }, 18 | "rules": { 19 | "strict": [ 20 | "error", 21 | "global" 22 | ], 23 | "indent": [ 24 | "error", 25 | 2 26 | ], 27 | "camelcase": "error", 28 | "no-console": "off", 29 | "no-unused-vars": [ 30 | "error", 31 | { 32 | "argsIgnorePattern": "^_" 33 | } 34 | ] 35 | } 36 | }, 37 | "eslintIgnore": [ 38 | "syntax_error.js", 39 | "node_modules", 40 | "async_init_package" 41 | ], 42 | "prettier": { 43 | "trailingComma": "all", 44 | "tabWidth": 2, 45 | "semi": true, 46 | "singleQuote": true 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/util/StdoutReporter.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const Mocha = require('mocha'); 8 | const { 9 | EVENT_RUN_BEGIN, 10 | EVENT_RUN_END, 11 | EVENT_TEST_FAIL, 12 | EVENT_TEST_PASS, 13 | EVENT_SUITE_BEGIN, 14 | EVENT_SUITE_END, 15 | } = Mocha.Runner.constants; 16 | 17 | /** 18 | * Custom reporter does not depend on any of the console.* functions, which 19 | * enables clean test output even when applying the lambda-runtime console 20 | * patch. 21 | */ 22 | module.exports = class StdoutReporter { 23 | constructor(runner) { 24 | this._alreadyWritten = false; 25 | this._report = ''; 26 | this._indents = 0; 27 | const stats = runner.stats; 28 | 29 | runner 30 | .once(EVENT_RUN_BEGIN, () => {}) 31 | .on(EVENT_SUITE_BEGIN, (suite) => { 32 | this.log(suite.title); 33 | this.increaseIndent(); 34 | }) 35 | .on(EVENT_SUITE_END, () => { 36 | this.decreaseIndent(); 37 | }) 38 | .on(EVENT_TEST_PASS, (test) => { 39 | this.log(`✓ ${test.title}`); 40 | }) 41 | .on(EVENT_TEST_FAIL, (test, err) => { 42 | this.log(`✗ ${test.title}`); 43 | this.increaseIndent(); 44 | err.stack.split('\n').forEach((msg) => this.log(msg)); 45 | this.decreaseIndent(); 46 | }) 47 | .once(EVENT_RUN_END, () => { 48 | this.log( 49 | 'Results ' + 50 | stats.passes + 51 | ' passed out of ' + 52 | (stats.passes + stats.failures) + 53 | ' total tests', 54 | ); 55 | this.dumpReport(); 56 | }); 57 | 58 | // This is hella nice if Mocha crashes for some reason 59 | // (which turns out is easy to do if you fool around with console.log) 60 | process.on('exit', () => this.dumpReport()); 61 | } 62 | 63 | indent() { 64 | return Array(this._indents).join(' '); 65 | } 66 | 67 | increaseIndent() { 68 | this._indents++; 69 | } 70 | 71 | decreaseIndent() { 72 | this._indents--; 73 | } 74 | 75 | log(line) { 76 | this._report += `${this.indent()}${line}\n`; 77 | } 78 | 79 | dumpReport() { 80 | if (!this._alreadyWritten) { 81 | process.stdout.write(this._report); 82 | this._alreadyWritten = true; 83 | } 84 | } 85 | }; 86 | --------------------------------------------------------------------------------