├── .dockerignore ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── test-on-push-and-pr.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── NOTICE ├── README.md ├── RELEASE.CHANGELOG.md ├── THIRD-PARTY-LICENSES ├── awslambdaric ├── __init__.py ├── __main__.py ├── bootstrap.py ├── lambda_context.py ├── lambda_literals.py ├── lambda_runtime_client.py ├── lambda_runtime_exception.py ├── lambda_runtime_hooks_runner.py ├── lambda_runtime_log_utils.py ├── lambda_runtime_marshaller.py └── runtime_client.cpp ├── deps ├── aws-lambda-cpp-0.2.6.tar.gz ├── curl-7.83.1.tar.gz ├── patches │ ├── aws-lambda-cpp-add-content-type.patch │ ├── aws-lambda-cpp-add-tenant-id.patch │ ├── aws-lambda-cpp-add-xray-response.patch │ ├── aws-lambda-cpp-make-lto-optional.patch │ ├── aws-lambda-cpp-make-the-runtime-client-user-agent-overrideable.patch │ ├── aws-lambda-cpp-posting-init-errors.patch │ └── libcurl-configure-template.patch └── versions ├── requirements ├── base.txt └── dev.txt ├── scripts ├── preinstall.sh └── update_deps.sh ├── setup.py └── tests ├── __init__.py ├── integration ├── codebuild-local │ ├── Dockerfile.agent │ ├── codebuild_build.sh │ ├── test_all.sh │ └── test_one.sh ├── codebuild │ ├── buildspec.os.alpine.yml │ ├── buildspec.os.amazonlinux.2.yml │ ├── buildspec.os.amazonlinux.2023.yml │ ├── buildspec.os.debian.yml │ └── buildspec.os.ubuntu.yml ├── docker-compose.template.yml ├── docker │ ├── Dockerfile.echo.alpine │ ├── Dockerfile.echo.amazonlinux2 │ ├── Dockerfile.echo.amazonlinux2023 │ ├── Dockerfile.echo.debian │ └── Dockerfile.echo.ubuntu ├── resources │ ├── aws-lambda-rie-arm64.tar.gz │ └── aws-lambda-rie.tar.gz └── test-handlers │ └── echo │ └── app.py ├── test_bootstrap.py ├── test_built_in_module_name └── sys.py ├── test_handler_with_slash └── test_handler.py ├── test_lambda_context.py ├── test_lambda_runtime_client.py ├── test_lambda_runtime_marshaller.py ├── test_main.py └── test_runtime_hooks.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | **/build/ 4 | **/node_modules/ 5 | **/dist/ 6 | 7 | tests/integration/* 8 | !tests/integration/resources/ 9 | !tests/integration/test-handlers/ 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | _Issue #, if available:_ 2 | 3 | _Description of changes:_ 4 | 5 | _Target (OCI, Managed Runtime, both):_ 6 | 7 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 8 | -------------------------------------------------------------------------------- /.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 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Run 'pr' target 16 | run: make pr 17 | 18 | alpine: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Run alpine integration tests 24 | run: DISTRO=alpine make test-integ 25 | 26 | amazonlinux: 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Run amazonlinux integration tests 32 | run: DISTRO=amazonlinux make test-integ 33 | 34 | debian: 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v4 39 | - name: Run debian integration tests 40 | run: DISTRO=debian make test-integ 41 | 42 | ubuntu: 43 | runs-on: ubuntu-latest 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | - name: Run ubuntu integration tests 48 | run: DISTRO=ubuntu make test-integ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | generated.docker-compose.*.yml 2 | 3 | tests/integration/resources/init 4 | 5 | .idea 6 | 7 | node_modules/ 8 | *.tsbuildinfo 9 | 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | wheels/ 32 | share/python-wheels/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | MANIFEST 37 | 38 | # PyInstaller 39 | # Usually these files are written by a python script from a template 40 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 41 | *.manifest 42 | *.spec 43 | 44 | # Installer logs 45 | pip-log.txt 46 | pip-delete-this-directory.txt 47 | 48 | # Unit test / coverage reports 49 | htmlcov/ 50 | .tox/ 51 | .nox/ 52 | .coverage 53 | .coverage.* 54 | .cache 55 | nosetests.xml 56 | coverage.xml 57 | *.cover 58 | *.py,cover 59 | .hypothesis/ 60 | .pytest_cache/ 61 | cover/ 62 | 63 | # Translations 64 | *.mo 65 | *.pot 66 | 67 | # Django stuff: 68 | *.log 69 | local_settings.py 70 | db.sqlite3 71 | db.sqlite3-journal 72 | 73 | # Flask stuff: 74 | instance/ 75 | .webassets-cache 76 | 77 | # Scrapy stuff: 78 | .scrapy 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # PyBuilder 84 | .pybuilder/ 85 | target/ 86 | 87 | # Jupyter Notebook 88 | .ipynb_checkpoints 89 | 90 | # IPython 91 | profile_default/ 92 | ipython_config.py 93 | 94 | # pyenv 95 | # For a library or package, you might want to ignore these files since the code is 96 | # intended to run in multiple environments; otherwise, check them in: 97 | # .python-version 98 | 99 | # pipenv 100 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 101 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 102 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 103 | # install all needed dependencies. 104 | #Pipfile.lock 105 | 106 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 107 | __pypackages__/ 108 | 109 | # Celery stuff 110 | celerybeat-schedule 111 | celerybeat.pid 112 | 113 | # SageMath parsed files 114 | *.sage.py 115 | 116 | # Environments 117 | .env 118 | .venv 119 | env/ 120 | venv/ 121 | ENV/ 122 | env.bak/ 123 | venv.bak/ 124 | 125 | # Spyder project settings 126 | .spyderproject 127 | .spyproject 128 | 129 | # Rope project settings 130 | .ropeproject 131 | 132 | # mkdocs documentation 133 | /site 134 | 135 | # mypy 136 | .mypy_cache/ 137 | .dmypy.json 138 | dmypy.json 139 | 140 | # Pyre type checker 141 | .pyre/ 142 | 143 | # pytype static type analyzer 144 | .pytype/ 145 | 146 | # Cython debug symbols 147 | cython_debug/ 148 | 149 | # Test files generated 150 | tmp*.py 151 | 152 | # dependencies 153 | deps/artifacts/ 154 | deps/aws-lambda-cpp-*/ 155 | deps/curl-*/ 156 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/python/black 3 | rev: 19.3b0 4 | hooks: 5 | - id: black 6 | language_version: python3.9 7 | exclude_types: ['markdown', 'ini', 'toml', 'rst'] 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include THIRD-PARTY-LICENSES 4 | include requirements/base.txt 5 | include awslambdaric/runtime_client.cpp 6 | recursive-include scripts * 7 | recursive-include deps * 8 | 9 | prune tests 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: target 2 | target: 3 | $(info ${HELP_MESSAGE}) 4 | @exit 0 5 | 6 | .PHONY: init 7 | init: 8 | pip3 install -r requirements/base.txt -r requirements/dev.txt 9 | 10 | .PHONY: test 11 | test: check-format 12 | pytest --cov awslambdaric --cov-report term-missing --cov-fail-under 90 tests 13 | 14 | .PHONY: setup-codebuild-agent 15 | setup-codebuild-agent: 16 | docker build -t codebuild-agent - < tests/integration/codebuild-local/Dockerfile.agent 17 | 18 | .PHONY: test-smoke 19 | test-smoke: setup-codebuild-agent 20 | CODEBUILD_IMAGE_TAG=codebuild-agent tests/integration/codebuild-local/test_one.sh tests/integration/codebuild/buildspec.os.alpine.yml alpine 3.15 3.9 21 | 22 | .PHONY: test-integ 23 | test-integ: setup-codebuild-agent 24 | CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild/. 25 | 26 | .PHONY: check-security 27 | check-security: 28 | bandit -r awslambdaric 29 | 30 | .PHONY: format 31 | format: 32 | black setup.py awslambdaric/ tests/ 33 | 34 | .PHONY: check-format 35 | check-format: 36 | black --check setup.py awslambdaric/ tests/ 37 | 38 | # Command to run everytime you make changes to verify everything works 39 | .PHONY: dev 40 | dev: init test 41 | 42 | # Verifications to run before sending a pull request 43 | .PHONY: pr 44 | pr: init check-format check-security dev 45 | 46 | codebuild: setup-codebuild-agent 47 | CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild 48 | 49 | .PHONY: clean 50 | clean: 51 | rm -rf dist 52 | rm -rf awslambdaric.egg-info 53 | 54 | .PHONY: build 55 | build: clean 56 | BUILD=true python3 setup.py sdist 57 | 58 | define HELP_MESSAGE 59 | 60 | Usage: $ make [TARGETS] 61 | 62 | TARGETS 63 | check-security Run bandit to find security issues. 64 | format Run black to automatically update your code to match our formatting. 65 | build Builds the package. 66 | clean Cleans the working directory by removing built artifacts. 67 | dev Run all development tests after a change. 68 | init Initialize and install the requirements and dev-requirements for this project. 69 | pr Perform all checks before submitting a Pull Request. 70 | test Run the Unit tests. 71 | 72 | endef 73 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AWS Lambda Python 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 Python Runtime Interface Client is vended through [pip](https://pypi.org/project/awslambdaric). 9 | You can include this package in your preferred base image to make that base image Lambda compatible. 10 | 11 | ## Requirements 12 | The Python Runtime Interface Client package currently supports Python versions: 13 | - 3.9.x up to and including 3.13.x 14 | 15 | ## Usage 16 | 17 | ### Creating a Docker Image for Lambda with the Runtime Interface Client 18 | First step is to choose the base image to be used. The supported Linux OS distributions are: 19 | 20 | - Amazon Linux 2 21 | - Alpine 22 | - Debian 23 | - Ubuntu 24 | 25 | 26 | Then, the Runtime Interface Client needs to be installed. We provide both wheel and source distribution. 27 | If the OS/pip version used does not support [manylinux2014](https://www.python.org/dev/peps/pep-0599/) wheels, you will also need to install the required build dependencies. 28 | Also, your Lambda function code needs to be copied into the image. 29 | 30 | ```dockerfile 31 | # Include global arg in this stage of the build 32 | ARG FUNCTION_DIR 33 | 34 | # Install aws-lambda-cpp build dependencies 35 | RUN apt-get update && \ 36 | apt-get install -y \ 37 | g++ \ 38 | make \ 39 | cmake \ 40 | unzip \ 41 | libcurl4-openssl-dev 42 | 43 | # Copy function code 44 | RUN mkdir -p ${FUNCTION_DIR} 45 | COPY app/* ${FUNCTION_DIR} 46 | 47 | # Install the function's dependencies 48 | RUN pip install \ 49 | --target ${FUNCTION_DIR} \ 50 | awslambdaric 51 | ``` 52 | 53 | 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. 54 | 55 | Example Dockerfile (to keep the image light we use a multi-stage build): 56 | ```dockerfile 57 | # Define custom function directory 58 | ARG FUNCTION_DIR="/function" 59 | 60 | FROM public.ecr.aws/docker/library/python:buster as build-image 61 | 62 | # Include global arg in this stage of the build 63 | ARG FUNCTION_DIR 64 | 65 | # Install aws-lambda-cpp build dependencies 66 | RUN apt-get update && \ 67 | apt-get install -y \ 68 | g++ \ 69 | make \ 70 | cmake \ 71 | unzip \ 72 | libcurl4-openssl-dev 73 | 74 | # Copy function code 75 | RUN mkdir -p ${FUNCTION_DIR} 76 | COPY app/* ${FUNCTION_DIR} 77 | 78 | # Install the function's dependencies 79 | RUN pip install \ 80 | --target ${FUNCTION_DIR} \ 81 | awslambdaric 82 | 83 | 84 | FROM public.ecr.aws/docker/library/python:buster 85 | 86 | # Include global arg in this stage of the build 87 | ARG FUNCTION_DIR 88 | # Set working directory to function root directory 89 | WORKDIR ${FUNCTION_DIR} 90 | 91 | # Copy in the built dependencies 92 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 93 | 94 | ENTRYPOINT [ "/usr/local/bin/python", "-m", "awslambdaric" ] 95 | CMD [ "app.handler" ] 96 | ``` 97 | 98 | Example Python handler `app.py`: 99 | ```python 100 | def handler(event, context): 101 | return "Hello World!" 102 | ``` 103 | 104 | ### Local Testing 105 | 106 | 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. 107 | 108 | *To install the emulator and test your Lambda function* 109 | 110 | 1) From your project directory, run the following command to download the RIE from GitHub and install it on your local machine. 111 | 112 | ```shell script 113 | mkdir -p ~/.aws-lambda-rie && \ 114 | curl -Lo ~/.aws-lambda-rie/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie && \ 115 | chmod +x ~/.aws-lambda-rie/aws-lambda-rie 116 | ``` 117 | 2) Run your Lambda image function using the docker run command. 118 | 119 | ```shell script 120 | docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \ 121 | --entrypoint /aws-lambda/aws-lambda-rie \ 122 | myfunction:latest \ 123 | /usr/local/bin/python -m awslambdaric app.handler 124 | ``` 125 | 126 | This runs the image as a container and starts up an endpoint locally at `http://localhost:9000/2015-03-31/functions/function/invocations`. 127 | 128 | 3) Post an event to the following endpoint using a curl command: 129 | 130 | ```shell script 131 | curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}' 132 | ``` 133 | 134 | This command invokes the function running in the container image and returns a response. 135 | 136 | *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).* 137 | 138 | 139 | ## Development 140 | 141 | ### Building the package 142 | Clone this repository and run: 143 | 144 | ```shell script 145 | make init 146 | make build 147 | ``` 148 | 149 | ### Running tests 150 | 151 | Make sure the project is built: 152 | ```shell script 153 | make init build 154 | ``` 155 | Then, 156 | * to run unit tests: `make test` 157 | * to run integration tests: `make test-integ` 158 | * to run smoke tests: `make test-smoke` 159 | 160 | ### Troubleshooting 161 | While running integration tests, you might encounter the Docker Hub rate limit error with the following body: 162 | ``` 163 | You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limits 164 | ``` 165 | To fix the above issue, consider authenticating to a Docker Hub account by setting the Docker Hub credentials as below CodeBuild environment variables. 166 | ```shell script 167 | DOCKERHUB_USERNAME= 168 | DOCKERHUB_PASSWORD= 169 | ``` 170 | Recommended way is to set the Docker Hub credentials in CodeBuild job by retrieving them from AWS Secrets Manager. 171 | ## Security 172 | 173 | 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. 174 | 175 | ## License 176 | 177 | This project is licensed under the Apache-2.0 License. 178 | -------------------------------------------------------------------------------- /RELEASE.CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### May 26, 2025 2 | `3.1.1` 3 | - Move unhandled exception warning message to init errors. ([#189](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/189)) 4 | 5 | ### May 21, 2025 6 | `3.1.0` 7 | - Add support for multi tenancy ([#187](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/187)) 8 | 9 | ### February 27, 2024 10 | `3.0.2` 11 | - Update `simplejson` to `3.20.1`([#184](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/184)) 12 | 13 | ### January 27, 2024 14 | `3.0.1` 15 | - Don't enforce text format on uncaught exception warning message ([#182](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/182)) 16 | 17 | ### November 19, 2024 18 | `3.0.0` 19 | - Drop support for deprecated python versions ([#179](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/179)) 20 | - Add support for snapstart runtime hooks ([#176](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/176)) 21 | 22 | ### August 23, 2024 23 | `2.2.1`: 24 | - Patch libcurl configure.ac to work with later versions of autoconf ([#166](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/168)) 25 | 26 | ### August 8, 2024 27 | 28 | `2.2.0`: 29 | 30 | - Propogate error type in header when reporting init error to RAPID ([#166](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/166)) 31 | 32 | ### July 31, 2024 33 | 34 | `2.1.0`: 35 | 36 | - Raise all init errors in init instead of suppressing them until the first invoke ([#163](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/163)) 37 | 38 | ### June 19, 2024 39 | 40 | `2.0.12`: 41 | 42 | - Relax simplejson dependency and keep it backwards compatible ([#153](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/152)) 43 | 44 | ### March 27, 2024 45 | 46 | `2.0.11`: 47 | 48 | - Upgrade simplejson to 3.18.4 ([#136](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/136)) 49 | 50 | ### February 13, 2024 51 | 52 | `2.0.10`: 53 | 54 | - Update format of unhandled exception warning message. ([#132](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/132)) 55 | 56 | ### February 01, 2024 57 | 58 | `2.0.9`: 59 | 60 | - Log warning on unhandled exceptions. ([#120](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/120)) 61 | 62 | ### October 30, 2023 63 | 64 | `2.0.8`: 65 | 66 | - Onboarded Python3.12 ([#118](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/118)) 67 | - Fix runtime_client blocking main thread from SIGTERM being handled. Enabled by default only for Python3.12 ([#115](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/115)) ([#124](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/124)) ([#125](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/125)) 68 | - Use unicode chars instead of escape sequences in json encoder output. Enabled by default only for Python3.12 ([#88](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/88)) ([#122](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/122)) 69 | - Cold start improvements ([#121](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/121)) 70 | 71 | ### August 29, 2023 72 | 73 | `2.0.7`: 74 | 75 | - Allow already structured logs in text format to use level-specific headers for logging protocol ([#111](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/111)) 76 | 77 | ### August 22, 2023 78 | 79 | `2.0.6`: 80 | 81 | - Add structured logging implementation ([#101](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/101)) 82 | 83 | ### August 16, 2023 84 | 85 | `2.0.5`: 86 | 87 | - Add support for Python3.11. ([#103](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/103)) 88 | - Add support for Python3.10. ([#102](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/102)) 89 | - Emit multi-line logs with timestamps.([#92](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/92)) 90 | - Remove importlib-metadata dependency.([#83](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/83)) 91 | 92 | ### May 25, 2022 93 | 94 | `2.0.4`: 95 | 96 | - Update os distro and runtime versions in compatibility tests, source base images from Amazon ECR Public ([#80](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/80)) 97 | - Improve error output for missing handler ([#70](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/70)) 98 | - Update curl to 7.83.1 ([#79](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/79)) 99 | 100 | ### May 4, 2022 101 | 102 | `2.0.3`: 103 | 104 | - Add changelog ([#75](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/75)) 105 | - Fix curl download url ([#74](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/74)) 106 | - Update curl to 7.83.0 ([#72](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/72)) 107 | 108 | ### Apr 7, 2022 109 | 110 | `2.0.2`: 111 | 112 | - Add leading zeros to the milliseconds part of a log timestamp ([#13](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/13)) 113 | - Use the raw fd directly rather than opening the fd pseudo file ([#56](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/56)) 114 | 115 | ### Jan 4, 2022 116 | 117 | `2.0.1`: 118 | 119 | - Add '--no-same-owner' option to all scripts tar commands ([#37](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/37)) 120 | 121 | ### Sep 29, 2021 122 | 123 | `2.0.0`: 124 | 125 | - Add arm64 architecture support ([#59](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/59)) 126 | - Update Curl to 7.78.0 ([#52](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/52)) 127 | 128 | ### Aug 23, 2021 129 | 130 | `1.2.2`: 131 | 132 | - Remove importlib.metadata dependency ([#55](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/55)) 133 | 134 | ### Aug 20, 2021 135 | 136 | `1.2.1`: 137 | 138 | - Remove logging for handler directory, as its adding un-necessary cloudwatch cost ([#51](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/51)) 139 | 140 | ### Jun 28, 2021 141 | 142 | `1.2.0`: 143 | 144 | - Move the `/` to `.` replacement only for import_module call ([#47](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/47)) 145 | - Add support for `/` in handler name ([#45](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/45)) 146 | - Add requestId in error response ([#40](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/40)) 147 | 148 | ### Jun 9, 2021 149 | 150 | `1.1.1`: 151 | 152 | - Update Curl version to 7.77.0 ([#33](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/35)) 153 | 154 | ### May 28, 2021 155 | 156 | `1.1.0`: 157 | 158 | - Release GIL when polling Runtime API for next invocation ([#33](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/33)) 159 | - Use importlib instead of deprecated imp module ([#28](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/28)) 160 | - Rename test directory ([#21](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/21)) 161 | - Revise fetching latest patch version of python ([#9](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/9)) 162 | - Update README.md examples: remove period from curl command ([#7](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/7)) 163 | - Add 'docker login' to fix pull rate limit issue ([#5](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/5)) 164 | - Include GitHub action on push and pr ([#3](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/3)) 165 | - Use Python 3.6 for Black ([#2](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/2)) 166 | - Tidy up setup.py ([#1](https://github.com/aws/aws-lambda-python-runtime-interface-client/pull/1)) 167 | 168 | ### Dec 01, 2020 169 | 170 | `1.0.0`: 171 | 172 | - Initial release of AWS Lambda Python Runtime Interface Client 173 | -------------------------------------------------------------------------------- /THIRD-PARTY-LICENSES: -------------------------------------------------------------------------------- 1 | ** aws-lambda-cpp; version 0.2.6 -- https://github.com/awslabs/aws-lambda-cpp 2 | 3 | Apache License 4 | 5 | Version 2.0, January 2004 6 | 7 | http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND 8 | DISTRIBUTION 9 | 10 | 1. Definitions. 11 | 12 | "License" shall mean the terms and conditions for use, reproduction, and 13 | distribution as defined by Sections 1 through 9 of this document. 14 | 15 | "Licensor" shall mean the copyright owner or entity authorized by the 16 | copyright owner that is granting the License. 17 | 18 | "Legal Entity" shall mean the union of the acting entity and all other 19 | entities that control, are controlled by, or are under common control 20 | with that entity. For the purposes of this definition, "control" means 21 | (i) the power, direct or indirect, to cause the direction or management 22 | of such entity, whether by contract or otherwise, or (ii) ownership of 23 | fifty percent (50%) or more of the outstanding shares, or (iii) 24 | beneficial ownership of such entity. 25 | 26 | "You" (or "Your") shall mean an individual or Legal Entity exercising 27 | permissions granted by this License. 28 | 29 | "Source" form shall mean the preferred form for making modifications, 30 | including but not limited to software source code, documentation source, 31 | and configuration files. 32 | 33 | "Object" form shall mean any form resulting from mechanical 34 | transformation or translation of a Source form, including but not limited 35 | to compiled object code, generated documentation, and conversions to 36 | other media types. 37 | 38 | "Work" shall mean the work of authorship, whether in Source or Object 39 | form, made available under the License, as indicated by a copyright 40 | notice that is included in or attached to the work (an example is 41 | provided in the Appendix below). 42 | 43 | "Derivative Works" shall mean any work, whether in Source or Object form, 44 | that is based on (or derived from) the Work and for which the editorial 45 | revisions, annotations, elaborations, or other modifications represent, 46 | as a whole, an original work of authorship. For the purposes of this 47 | License, Derivative Works shall not include works that remain separable 48 | from, or merely link (or bind by name) to the interfaces of, the Work and 49 | Derivative Works thereof. 50 | 51 | "Contribution" shall mean any work of authorship, including the original 52 | version of the Work and any modifications or additions to that Work or 53 | Derivative Works thereof, that is intentionally submitted to Licensor for 54 | inclusion in the Work by the copyright owner or by an individual or Legal 55 | Entity authorized to submit on behalf of the copyright owner. For the 56 | purposes of this definition, "submitted" means any form of electronic, 57 | verbal, or written communication sent to the Licensor or its 58 | representatives, including but not limited to communication on electronic 59 | mailing lists, source code control systems, and issue tracking systems 60 | that are managed by, or on behalf of, the Licensor for the purpose of 61 | discussing and improving the Work, but excluding communication that is 62 | conspicuously marked or otherwise designated in writing by the copyright 63 | owner as "Not a Contribution." 64 | 65 | "Contributor" shall mean Licensor and any individual or Legal Entity on 66 | behalf of whom a Contribution has been received by Licensor and 67 | subsequently incorporated within the Work. 68 | 69 | 2. Grant of Copyright License. Subject to the terms and conditions of this 70 | License, each Contributor hereby grants to You a perpetual, worldwide, 71 | non-exclusive, no-charge, royalty-free, irrevocable copyright license to 72 | reproduce, prepare Derivative Works of, publicly display, publicly perform, 73 | sublicense, and distribute the Work and such Derivative Works in Source or 74 | Object form. 75 | 76 | 3. Grant of Patent License. Subject to the terms and conditions of this 77 | License, each Contributor hereby grants to You a perpetual, worldwide, 78 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in 79 | this section) patent license to make, have made, use, offer to sell, sell, 80 | import, and otherwise transfer the Work, where such license applies only to 81 | those patent claims licensable by such Contributor that are necessarily 82 | infringed by their Contribution(s) alone or by combination of their 83 | Contribution(s) with the Work to which such Contribution(s) was submitted. 84 | If You institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 86 | Contribution incorporated within the Work constitutes direct or contributory 87 | patent infringement, then any patent licenses granted to You under this 88 | License for that Work shall terminate as of the date such litigation is 89 | filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the Work or 92 | Derivative Works thereof in any medium, with or without modifications, and 93 | in Source or Object form, provided that You meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or Derivative Works a 96 | copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices stating 99 | that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works that You 102 | distribute, all copyright, patent, trademark, and attribution notices 103 | from the Source form of the Work, excluding those notices that do not 104 | pertain to any part of the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must include 108 | a readable copy of the attribution notices contained within such NOTICE 109 | file, excluding those notices that do not pertain to any part of the 110 | Derivative Works, in at least one of the following places: within a 111 | NOTICE text file distributed as part of the Derivative Works; within the 112 | Source form or documentation, if provided along with the Derivative 113 | Works; or, within a display generated by the Derivative Works, if and 114 | wherever such third-party notices normally appear. The contents of the 115 | NOTICE file are for informational purposes only and do not modify the 116 | License. You may add Your own attribution notices within Derivative Works 117 | that You distribute, alongside or as an addendum to the NOTICE text from 118 | the Work, provided that such additional attribution notices cannot be 119 | construed as modifying the License. 120 | 121 | You may add Your own copyright statement to Your modifications and may 122 | provide additional or different license terms and conditions for use, 123 | reproduction, or distribution of Your modifications, or for any such 124 | Derivative Works as a whole, provided Your use, reproduction, and 125 | distribution of the Work otherwise complies with the conditions stated in 126 | this License. 127 | 128 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 129 | Contribution intentionally submitted for inclusion in the Work by You to the 130 | Licensor shall be under the terms and conditions of this License, without 131 | any additional terms or conditions. Notwithstanding the above, nothing 132 | herein shall supersede or modify the terms of any separate license agreement 133 | you may have executed with Licensor regarding such Contributions. 134 | 135 | 6. Trademarks. This License does not grant permission to use the trade 136 | names, trademarks, service marks, or product names of the Licensor, except 137 | as required for reasonable and customary use in describing the origin of the 138 | Work and reproducing the content of the NOTICE file. 139 | 140 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in 141 | writing, Licensor provides the Work (and each Contributor provides its 142 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 143 | KIND, either express or implied, including, without limitation, any 144 | warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or 145 | FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining 146 | the appropriateness of using or redistributing the Work and assume any risks 147 | associated with Your exercise of permissions under this License. 148 | 149 | 8. Limitation of Liability. In no event and under no legal theory, whether 150 | in tort (including negligence), contract, or otherwise, unless required by 151 | applicable law (such as deliberate and grossly negligent acts) or agreed to 152 | in writing, shall any Contributor be liable to You for damages, including 153 | any direct, indirect, special, incidental, or consequential damages of any 154 | character arising as a result of this License or out of the use or inability 155 | to use the Work (including but not limited to damages for loss of goodwill, 156 | work stoppage, computer failure or malfunction, or any and all other 157 | commercial damages or losses), even if such Contributor has been advised of 158 | the possibility of such damages. 159 | 160 | 9. Accepting Warranty or Additional Liability. While redistributing the Work 161 | or Derivative Works thereof, You may choose to offer, and charge a fee for, 162 | acceptance of support, warranty, indemnity, or other liability obligations 163 | and/or rights consistent with this License. However, in accepting such 164 | obligations, You may act only on Your own behalf and on Your sole 165 | responsibility, not on behalf of any other Contributor, and only if You 166 | agree to indemnify, defend, and hold each Contributor harmless for any 167 | liability incurred by, or claims asserted against, such Contributor by 168 | reason of your accepting any such warranty or additional liability. END OF 169 | TERMS AND CONDITIONS 170 | 171 | APPENDIX: How to apply the Apache License to your work. 172 | 173 | To apply the Apache License to your work, attach the following boilerplate 174 | notice, with the fields enclosed by brackets "[]" replaced with your own 175 | identifying information. (Don't include the brackets!) The text should be 176 | enclosed in the appropriate comment syntax for the file format. We also 177 | recommend that a file or class name and description of purpose be included on 178 | the same "printed page" as the copyright notice for easier identification 179 | within third-party archives. 180 | 181 | Copyright [yyyy] [name of copyright owner] 182 | 183 | Licensed under the Apache License, Version 2.0 (the "License"); 184 | 185 | you may not use this file except in compliance with the License. 186 | 187 | You may obtain a copy of the License at 188 | 189 | http://www.apache.org/licenses/LICENSE-2.0 190 | 191 | Unless required by applicable law or agreed to in writing, software 192 | 193 | distributed under the License is distributed on an "AS IS" BASIS, 194 | 195 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 196 | 197 | See the License for the specific language governing permissions and 198 | 199 | limitations under the License. 200 | 201 | * For aws-lambda-cpp see also this required NOTICE: 202 | AWS Lambda Cpp Runtime 203 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 204 | 205 | ------ 206 | 207 | ** python-simplejson; version 3.17.2 -- 208 | https://github.com/simplejson/simplejson 209 | Copyright (c) 2006 Bob Ippolito 210 | 211 | Permission is hereby granted, free of charge, to any person obtaining a copy of 212 | this software and associated documentation files (the "Software"), to deal in 213 | the Software without restriction, including without limitation the rights to 214 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 215 | of the Software, and to permit persons to whom the Software is furnished to do 216 | so, subject to the following conditions: 217 | 218 | The above copyright notice and this permission notice shall be included in all 219 | copies or substantial portions of the Software. 220 | 221 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 222 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 223 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 224 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 225 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 226 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 227 | SOFTWARE. 228 | 229 | ------ 230 | 231 | ** libcurl; version 7.83.1 -- https://github.com/curl/curl 232 | Copyright (c) 1996 - 2022, Daniel Stenberg, daniel@haxx.se, and many 233 | contributors, see the THANKS file. 234 | 235 | All rights reserved. 236 | 237 | COPYRIGHT AND PERMISSION NOTICE 238 | 239 | Copyright (c) 1996 - 2022, Daniel Stenberg, daniel@haxx.se, and many 240 | contributors, see the THANKS file. 241 | 242 | All rights reserved. 243 | 244 | Permission to use, copy, modify, and distribute this software for any purpose 245 | with or without fee is hereby granted, provided that the above copyright 246 | notice and this permission notice appear in all copies. 247 | 248 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 249 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 250 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN 251 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 252 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 253 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 254 | OR OTHER DEALINGS IN THE SOFTWARE. 255 | 256 | Except as contained in this notice, the name of a copyright holder shall not 257 | be used in advertising or otherwise to promote the sale, use or other dealings 258 | in this Software without prior written authorization of the copyright holder. -------------------------------------------------------------------------------- /awslambdaric/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | __version__ = "3.1.1" 6 | -------------------------------------------------------------------------------- /awslambdaric/__main__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | import os 6 | import sys 7 | 8 | from . import bootstrap 9 | 10 | 11 | def main(args): 12 | app_root = os.getcwd() 13 | 14 | try: 15 | handler = args[1] 16 | except IndexError: 17 | raise ValueError("Handler not set") 18 | 19 | lambda_runtime_api_addr = os.environ["AWS_LAMBDA_RUNTIME_API"] 20 | 21 | bootstrap.run(app_root, handler, lambda_runtime_api_addr) 22 | 23 | 24 | if __name__ == "__main__": 25 | main(sys.argv) 26 | -------------------------------------------------------------------------------- /awslambdaric/bootstrap.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | import importlib 6 | import json 7 | import logging 8 | import os 9 | import sys 10 | import time 11 | import traceback 12 | 13 | from .lambda_context import LambdaContext 14 | from .lambda_runtime_client import LambdaRuntimeClient 15 | from .lambda_runtime_exception import FaultException 16 | from .lambda_runtime_log_utils import ( 17 | _DATETIME_FORMAT, 18 | _DEFAULT_FRAME_TYPE, 19 | _JSON_FRAME_TYPES, 20 | _TEXT_FRAME_TYPES, 21 | JsonFormatter, 22 | LogFormat, 23 | _format_log_level, 24 | _get_log_level_from_env_var, 25 | ) 26 | from .lambda_runtime_marshaller import to_json 27 | 28 | ERROR_LOG_LINE_TERMINATE = "\r" 29 | ERROR_LOG_IDENT = "\u00a0" # NO-BREAK SPACE U+00A0 30 | _AWS_LAMBDA_LOG_FORMAT = LogFormat.from_str(os.environ.get("AWS_LAMBDA_LOG_FORMAT")) 31 | _AWS_LAMBDA_LOG_LEVEL = _get_log_level_from_env_var( 32 | os.environ.get("AWS_LAMBDA_LOG_LEVEL") 33 | ) 34 | AWS_LAMBDA_INITIALIZATION_TYPE = "AWS_LAMBDA_INITIALIZATION_TYPE" 35 | INIT_TYPE_SNAP_START = "snap-start" 36 | 37 | 38 | def _get_handler(handler): 39 | try: 40 | (modname, fname) = handler.rsplit(".", 1) 41 | except ValueError as e: 42 | raise FaultException( 43 | FaultException.MALFORMED_HANDLER_NAME, 44 | "Bad handler '{}': {}".format(handler, str(e)), 45 | ) 46 | 47 | try: 48 | if modname.split(".")[0] in sys.builtin_module_names: 49 | raise FaultException( 50 | FaultException.BUILT_IN_MODULE_CONFLICT, 51 | "Cannot use built-in module {} as a handler module".format(modname), 52 | ) 53 | m = importlib.import_module(modname.replace("/", ".")) 54 | except ImportError as e: 55 | raise FaultException( 56 | FaultException.IMPORT_MODULE_ERROR, 57 | "Unable to import module '{}': {}".format(modname, str(e)), 58 | ) 59 | except SyntaxError as e: 60 | trace = [' File "%s" Line %s\n %s' % (e.filename, e.lineno, e.text)] 61 | raise FaultException( 62 | FaultException.USER_CODE_SYNTAX_ERROR, 63 | "Syntax error in module '{}': {}".format(modname, str(e)), 64 | trace, 65 | ) 66 | 67 | try: 68 | request_handler = getattr(m, fname) 69 | except AttributeError: 70 | fault = FaultException( 71 | FaultException.HANDLER_NOT_FOUND, 72 | "Handler '{}' missing on module '{}'".format(fname, modname), 73 | None, 74 | ) 75 | raise fault 76 | return request_handler 77 | 78 | 79 | def make_error( 80 | error_message, 81 | error_type, 82 | stack_trace, 83 | invoke_id=None, 84 | ): 85 | result = { 86 | "errorMessage": error_message if error_message else "", 87 | "errorType": error_type if error_type else "", 88 | "requestId": invoke_id if invoke_id is not None else "", 89 | "stackTrace": stack_trace if stack_trace else [], 90 | } 91 | return result 92 | 93 | 94 | def replace_line_indentation(line, indent_char, new_indent_char): 95 | ident_chars_count = 0 96 | for c in line: 97 | if c != indent_char: 98 | break 99 | ident_chars_count += 1 100 | return (new_indent_char * ident_chars_count) + line[ident_chars_count:] 101 | 102 | 103 | if _AWS_LAMBDA_LOG_FORMAT == LogFormat.JSON: 104 | _ERROR_FRAME_TYPE = _JSON_FRAME_TYPES[logging.ERROR] 105 | 106 | def log_error(error_result, log_sink): 107 | error_result = { 108 | "timestamp": time.strftime( 109 | _DATETIME_FORMAT, logging.Formatter.converter(time.time()) 110 | ), 111 | "log_level": "ERROR", 112 | **error_result, 113 | } 114 | log_sink.log_error( 115 | [to_json(error_result)], 116 | ) 117 | 118 | else: 119 | _ERROR_FRAME_TYPE = _TEXT_FRAME_TYPES[logging.ERROR] 120 | 121 | def log_error(error_result, log_sink): 122 | error_description = "[ERROR]" 123 | 124 | error_result_type = error_result.get("errorType") 125 | if error_result_type: 126 | error_description += " " + error_result_type 127 | 128 | error_result_message = error_result.get("errorMessage") 129 | if error_result_message: 130 | if error_result_type: 131 | error_description += ":" 132 | error_description += " " + error_result_message 133 | 134 | error_message_lines = [error_description] 135 | 136 | stack_trace = error_result.get("stackTrace") 137 | if stack_trace is not None: 138 | error_message_lines += ["Traceback (most recent call last):"] 139 | for trace_element in stack_trace: 140 | if trace_element == "": 141 | error_message_lines += [""] 142 | else: 143 | for trace_line in trace_element.splitlines(): 144 | error_message_lines += [ 145 | replace_line_indentation(trace_line, " ", ERROR_LOG_IDENT) 146 | ] 147 | 148 | log_sink.log_error(error_message_lines) 149 | 150 | 151 | def handle_event_request( 152 | lambda_runtime_client, 153 | request_handler, 154 | invoke_id, 155 | event_body, 156 | content_type, 157 | client_context_json, 158 | cognito_identity_json, 159 | invoked_function_arn, 160 | epoch_deadline_time_in_ms, 161 | tenant_id, 162 | log_sink, 163 | ): 164 | error_result = None 165 | try: 166 | lambda_context = create_lambda_context( 167 | client_context_json, 168 | cognito_identity_json, 169 | epoch_deadline_time_in_ms, 170 | invoke_id, 171 | invoked_function_arn, 172 | tenant_id, 173 | ) 174 | event = lambda_runtime_client.marshaller.unmarshal_request( 175 | event_body, content_type 176 | ) 177 | response = request_handler(event, lambda_context) 178 | result, result_content_type = lambda_runtime_client.marshaller.marshal_response( 179 | response 180 | ) 181 | except FaultException as e: 182 | xray_fault = make_xray_fault("LambdaValidationError", e.msg, os.getcwd(), []) 183 | error_result = make_error( 184 | e.msg, 185 | e.exception_type, 186 | e.trace, 187 | invoke_id, 188 | ) 189 | 190 | except Exception: 191 | etype, value, tb = sys.exc_info() 192 | tb_tuples = extract_traceback(tb) 193 | for i in range(len(tb_tuples)): 194 | if "/bootstrap.py" not in tb_tuples[i][0]: # filename of the tb tuple 195 | tb_tuples = tb_tuples[i:] 196 | break 197 | 198 | xray_fault = make_xray_fault(etype.__name__, str(value), os.getcwd(), tb_tuples) 199 | error_result = make_error( 200 | str(value), etype.__name__, traceback.format_list(tb_tuples), invoke_id 201 | ) 202 | 203 | if error_result is not None: 204 | 205 | log_error(error_result, log_sink) 206 | lambda_runtime_client.post_invocation_error( 207 | invoke_id, to_json(error_result), to_json(xray_fault) 208 | ) 209 | else: 210 | lambda_runtime_client.post_invocation_result( 211 | invoke_id, result, result_content_type 212 | ) 213 | 214 | 215 | def parse_json_header(header, name): 216 | try: 217 | return json.loads(header) 218 | except Exception as e: 219 | raise FaultException( 220 | FaultException.LAMBDA_CONTEXT_UNMARSHAL_ERROR, 221 | "Unable to parse {} JSON: {}".format(name, str(e)), 222 | None, 223 | ) 224 | 225 | 226 | def create_lambda_context( 227 | client_context_json, 228 | cognito_identity_json, 229 | epoch_deadline_time_in_ms, 230 | invoke_id, 231 | invoked_function_arn, 232 | tenant_id, 233 | ): 234 | client_context = None 235 | if client_context_json: 236 | client_context = parse_json_header(client_context_json, "Client Context") 237 | cognito_identity = None 238 | if cognito_identity_json: 239 | cognito_identity = parse_json_header(cognito_identity_json, "Cognito Identity") 240 | 241 | return LambdaContext( 242 | invoke_id, 243 | client_context, 244 | cognito_identity, 245 | epoch_deadline_time_in_ms, 246 | invoked_function_arn, 247 | tenant_id, 248 | ) 249 | 250 | 251 | def build_fault_result(exc_info, msg): 252 | etype, value, tb = exc_info 253 | tb_tuples = extract_traceback(tb) 254 | for i in range(len(tb_tuples)): 255 | if "/bootstrap.py" not in tb_tuples[i][0]: # filename of the tb tuple 256 | tb_tuples = tb_tuples[i:] 257 | break 258 | 259 | return make_error( 260 | msg if msg else str(value), 261 | etype.__name__, 262 | traceback.format_list(tb_tuples), 263 | ) 264 | 265 | 266 | def make_xray_fault(ex_type, ex_msg, working_dir, tb_tuples): 267 | stack = [] 268 | files = set() 269 | for t in tb_tuples: 270 | tb_file, tb_line, tb_method, tb_code = t 271 | tb_xray = {"label": tb_method, "path": tb_file, "line": tb_line} 272 | stack.append(tb_xray) 273 | files.add(tb_file) 274 | 275 | formatted_ex = {"message": ex_msg, "type": ex_type, "stack": stack} 276 | xray_fault = { 277 | "working_directory": working_dir, 278 | "exceptions": [formatted_ex], 279 | "paths": list(files), 280 | } 281 | return xray_fault 282 | 283 | 284 | def extract_traceback(tb): 285 | return [ 286 | (frame.filename, frame.lineno, frame.name, frame.line) 287 | for frame in traceback.extract_tb(tb) 288 | ] 289 | 290 | 291 | def on_init_complete(lambda_runtime_client, log_sink): 292 | from . import lambda_runtime_hooks_runner 293 | 294 | try: 295 | lambda_runtime_hooks_runner.run_before_snapshot() 296 | lambda_runtime_client.restore_next() 297 | except: 298 | error_result = build_fault_result(sys.exc_info(), None) 299 | log_error(error_result, log_sink) 300 | lambda_runtime_client.post_init_error( 301 | error_result, FaultException.BEFORE_SNAPSHOT_ERROR 302 | ) 303 | sys.exit(64) 304 | 305 | try: 306 | lambda_runtime_hooks_runner.run_after_restore() 307 | except: 308 | error_result = build_fault_result(sys.exc_info(), None) 309 | log_error(error_result, log_sink) 310 | lambda_runtime_client.report_restore_error(error_result) 311 | sys.exit(65) 312 | 313 | 314 | class LambdaLoggerHandler(logging.Handler): 315 | def __init__(self, log_sink): 316 | logging.Handler.__init__(self) 317 | self.log_sink = log_sink 318 | 319 | def emit(self, record): 320 | msg = self.format(record) 321 | self.log_sink.log(msg) 322 | 323 | 324 | class LambdaLoggerHandlerWithFrameType(logging.Handler): 325 | def __init__(self, log_sink): 326 | super().__init__() 327 | self.log_sink = log_sink 328 | 329 | def emit(self, record): 330 | self.log_sink.log( 331 | self.format(record), 332 | frame_type=( 333 | getattr(record, "_frame_type", None) 334 | or _TEXT_FRAME_TYPES.get(_format_log_level(record)) 335 | ), 336 | ) 337 | 338 | 339 | class LambdaLoggerFilter(logging.Filter): 340 | def filter(self, record): 341 | record.aws_request_id = _GLOBAL_AWS_REQUEST_ID or "" 342 | record.tenant_id = _GLOBAL_TENANT_ID 343 | return True 344 | 345 | 346 | class Unbuffered(object): 347 | def __init__(self, stream): 348 | self.stream = stream 349 | 350 | def __enter__(self): 351 | return self 352 | 353 | def __exit__(self, exc_type, exc_value, exc_tb): 354 | pass 355 | 356 | def __getattr__(self, attr): 357 | return getattr(self.stream, attr) 358 | 359 | def write(self, msg): 360 | self.stream.write(msg) 361 | self.stream.flush() 362 | 363 | def writelines(self, msgs): 364 | self.stream.writelines(msgs) 365 | self.stream.flush() 366 | 367 | 368 | class StandardLogSink(object): 369 | def __init__(self): 370 | pass 371 | 372 | def __enter__(self): 373 | return self 374 | 375 | def __exit__(self, exc_type, exc_value, exc_tb): 376 | pass 377 | 378 | def log(self, msg, frame_type=None): 379 | sys.stdout.write(msg) 380 | 381 | def log_error(self, message_lines): 382 | error_message = ERROR_LOG_LINE_TERMINATE.join(message_lines) + "\n" 383 | sys.stdout.write(error_message) 384 | 385 | 386 | class FramedTelemetryLogSink(object): 387 | """ 388 | FramedTelemetryLogSink implements the logging contract between runtimes and the platform. It implements a simple 389 | framing protocol so message boundaries can be determined. Each frame can be visualized as follows: 390 |
391 |     {@code
392 |     +----------------------+------------------------+---------------------+-----------------------+
393 |     | Frame Type - 4 bytes | Length (len) - 4 bytes | Timestamp - 8 bytes | Message - 'len' bytes |
394 |     +----------------------+------------------------+---------------------+-----------------------+
395 |     }
396 |     
397 | The first 4 bytes indicate the type of the frame - log frames have a type defined as the hex value 0xa55a0003. The 398 | second 4 bytes should indicate the message's length. The next 8 bytes should indicate the timestamp of the message. 399 | The next 'len' bytes contain the message. The byte order is big-endian. 400 | """ 401 | 402 | def __init__(self, fd): 403 | self.fd = int(fd) 404 | 405 | def __enter__(self): 406 | self.file = os.fdopen(self.fd, "wb", 0) 407 | return self 408 | 409 | def __exit__(self, exc_type, exc_value, exc_tb): 410 | self.file.close() 411 | 412 | def log(self, msg, frame_type=None): 413 | encoded_msg = msg.encode("utf8") 414 | 415 | timestamp = int(time.time_ns() / 1000) # UNIX timestamp in microseconds 416 | log_msg = ( 417 | (frame_type or _DEFAULT_FRAME_TYPE) 418 | + len(encoded_msg).to_bytes(4, "big") 419 | + timestamp.to_bytes(8, "big") 420 | + encoded_msg 421 | ) 422 | self.file.write(log_msg) 423 | 424 | def log_error(self, message_lines): 425 | error_message = "\n".join(message_lines) 426 | self.log( 427 | error_message, 428 | frame_type=_ERROR_FRAME_TYPE, 429 | ) 430 | 431 | 432 | def update_xray_env_variable(xray_trace_id): 433 | if xray_trace_id is not None: 434 | os.environ["_X_AMZN_TRACE_ID"] = xray_trace_id 435 | else: 436 | if "_X_AMZN_TRACE_ID" in os.environ: 437 | del os.environ["_X_AMZN_TRACE_ID"] 438 | 439 | 440 | def create_log_sink(): 441 | if "_LAMBDA_TELEMETRY_LOG_FD" in os.environ: 442 | fd = os.environ["_LAMBDA_TELEMETRY_LOG_FD"] 443 | del os.environ["_LAMBDA_TELEMETRY_LOG_FD"] 444 | return FramedTelemetryLogSink(fd) 445 | 446 | else: 447 | return StandardLogSink() 448 | 449 | 450 | _GLOBAL_AWS_REQUEST_ID = None 451 | _GLOBAL_TENANT_ID = None 452 | 453 | 454 | def _setup_logging(log_format, log_level, log_sink): 455 | logging.Formatter.converter = time.gmtime 456 | logger = logging.getLogger() 457 | 458 | if log_format == LogFormat.JSON or log_level: 459 | logger_handler = LambdaLoggerHandlerWithFrameType(log_sink) 460 | else: 461 | logger_handler = LambdaLoggerHandler(log_sink) 462 | 463 | if log_format == LogFormat.JSON: 464 | logger_handler.setFormatter(JsonFormatter()) 465 | else: 466 | logger_handler.setFormatter( 467 | logging.Formatter( 468 | "[%(levelname)s]\t%(asctime)s.%(msecs)03dZ\t%(aws_request_id)s\t%(message)s\n", 469 | "%Y-%m-%dT%H:%M:%S", 470 | ) 471 | ) 472 | 473 | if log_level in logging._nameToLevel: 474 | logger.setLevel(log_level) 475 | 476 | logger_handler.addFilter(LambdaLoggerFilter()) 477 | logger.addHandler(logger_handler) 478 | 479 | 480 | def run(app_root, handler, lambda_runtime_api_addr): 481 | sys.stdout = Unbuffered(sys.stdout) 482 | sys.stderr = Unbuffered(sys.stderr) 483 | 484 | use_thread_for_polling_next = os.environ.get("AWS_EXECUTION_ENV") in { 485 | "AWS_Lambda_python3.12", 486 | "AWS_Lambda_python3.13", 487 | } 488 | 489 | with create_log_sink() as log_sink: 490 | lambda_runtime_client = LambdaRuntimeClient( 491 | lambda_runtime_api_addr, use_thread_for_polling_next 492 | ) 493 | error_result = None 494 | 495 | try: 496 | _setup_logging(_AWS_LAMBDA_LOG_FORMAT, _AWS_LAMBDA_LOG_LEVEL, log_sink) 497 | global _GLOBAL_AWS_REQUEST_ID, _GLOBAL_TENANT_ID 498 | 499 | request_handler = _get_handler(handler) 500 | except FaultException as e: 501 | error_result = make_error( 502 | e.msg, 503 | e.exception_type, 504 | e.trace, 505 | ) 506 | except Exception: 507 | error_result = build_fault_result(sys.exc_info(), None) 508 | 509 | if error_result is not None: 510 | from .lambda_literals import lambda_unhandled_exception_warning_message 511 | 512 | logging.warning(lambda_unhandled_exception_warning_message) 513 | log_error(error_result, log_sink) 514 | lambda_runtime_client.post_init_error(error_result) 515 | 516 | sys.exit(1) 517 | 518 | if os.environ.get(AWS_LAMBDA_INITIALIZATION_TYPE) == INIT_TYPE_SNAP_START: 519 | on_init_complete(lambda_runtime_client, log_sink) 520 | 521 | while True: 522 | event_request = lambda_runtime_client.wait_next_invocation() 523 | 524 | _GLOBAL_AWS_REQUEST_ID = event_request.invoke_id 525 | _GLOBAL_TENANT_ID = event_request.tenant_id 526 | 527 | update_xray_env_variable(event_request.x_amzn_trace_id) 528 | 529 | handle_event_request( 530 | lambda_runtime_client, 531 | request_handler, 532 | event_request.invoke_id, 533 | event_request.event_body, 534 | event_request.content_type, 535 | event_request.client_context, 536 | event_request.cognito_identity, 537 | event_request.invoked_function_arn, 538 | event_request.deadline_time_in_ms, 539 | event_request.tenant_id, 540 | log_sink, 541 | ) 542 | -------------------------------------------------------------------------------- /awslambdaric/lambda_context.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | import logging 6 | import os 7 | import sys 8 | import time 9 | 10 | 11 | class LambdaContext(object): 12 | def __init__( 13 | self, 14 | invoke_id, 15 | client_context, 16 | cognito_identity, 17 | epoch_deadline_time_in_ms, 18 | invoked_function_arn=None, 19 | tenant_id=None, 20 | ): 21 | self.aws_request_id = invoke_id 22 | self.log_group_name = os.environ.get("AWS_LAMBDA_LOG_GROUP_NAME") 23 | self.log_stream_name = os.environ.get("AWS_LAMBDA_LOG_STREAM_NAME") 24 | self.function_name = os.environ.get("AWS_LAMBDA_FUNCTION_NAME") 25 | self.memory_limit_in_mb = os.environ.get("AWS_LAMBDA_FUNCTION_MEMORY_SIZE") 26 | self.function_version = os.environ.get("AWS_LAMBDA_FUNCTION_VERSION") 27 | self.invoked_function_arn = invoked_function_arn 28 | self.tenant_id = tenant_id 29 | 30 | self.client_context = make_obj_from_dict(ClientContext, client_context) 31 | if self.client_context is not None: 32 | self.client_context.client = make_obj_from_dict( 33 | Client, self.client_context.client 34 | ) 35 | 36 | self.identity = make_obj_from_dict(CognitoIdentity, {}) 37 | if cognito_identity is not None: 38 | self.identity.cognito_identity_id = cognito_identity.get( 39 | "cognitoIdentityId" 40 | ) 41 | self.identity.cognito_identity_pool_id = cognito_identity.get( 42 | "cognitoIdentityPoolId" 43 | ) 44 | 45 | self._epoch_deadline_time_in_ms = epoch_deadline_time_in_ms 46 | 47 | def get_remaining_time_in_millis(self): 48 | epoch_now_in_ms = int(time.time() * 1000) 49 | delta_ms = self._epoch_deadline_time_in_ms - epoch_now_in_ms 50 | return delta_ms if delta_ms > 0 else 0 51 | 52 | def log(self, msg): 53 | for handler in logging.getLogger().handlers: 54 | if hasattr(handler, "log_sink"): 55 | handler.log_sink.log(str(msg)) 56 | return 57 | sys.stdout.write(str(msg)) 58 | 59 | def __repr__(self): 60 | return ( 61 | f"{self.__class__.__name__}([" 62 | f"aws_request_id={self.aws_request_id}," 63 | f"log_group_name={self.log_group_name}," 64 | f"log_stream_name={self.log_stream_name}," 65 | f"function_name={self.function_name}," 66 | f"memory_limit_in_mb={self.memory_limit_in_mb}," 67 | f"function_version={self.function_version}," 68 | f"invoked_function_arn={self.invoked_function_arn}," 69 | f"client_context={self.client_context}," 70 | f"identity={self.identity}," 71 | f"tenant_id={self.tenant_id}" 72 | "])" 73 | ) 74 | 75 | 76 | class CognitoIdentity(object): 77 | __slots__ = ["cognito_identity_id", "cognito_identity_pool_id"] 78 | 79 | def __repr__(self): 80 | return ( 81 | f"{self.__class__.__name__}([" 82 | f"cognito_identity_id={self.cognito_identity_id}," 83 | f"cognito_identity_pool_id={self.cognito_identity_pool_id}" 84 | "])" 85 | ) 86 | 87 | 88 | class Client(object): 89 | __slots__ = [ 90 | "installation_id", 91 | "app_title", 92 | "app_version_name", 93 | "app_version_code", 94 | "app_package_name", 95 | ] 96 | 97 | def __repr__(self): 98 | return ( 99 | f"{self.__class__.__name__}([" 100 | f"installation_id={self.installation_id}," 101 | f"app_title={self.app_title}," 102 | f"app_version_name={self.app_version_name}," 103 | f"app_version_code={self.app_version_code}," 104 | f"app_package_name={self.app_package_name}" 105 | "])" 106 | ) 107 | 108 | 109 | class ClientContext(object): 110 | __slots__ = ["custom", "env", "client"] 111 | 112 | def __repr__(self): 113 | return ( 114 | f"{self.__class__.__name__}([" 115 | f"custom={self.custom}," 116 | f"env={self.env}," 117 | f"client={self.client}" 118 | "])" 119 | ) 120 | 121 | 122 | def make_obj_from_dict(_class, _dict, fields=None): 123 | if _dict is None: 124 | return None 125 | obj = _class() 126 | set_obj_from_dict(obj, _dict) 127 | return obj 128 | 129 | 130 | def set_obj_from_dict(obj, _dict, fields=None): 131 | if fields is None: 132 | fields = obj.__class__.__slots__ 133 | for field in fields: 134 | setattr(obj, field, _dict.get(field, None)) 135 | -------------------------------------------------------------------------------- /awslambdaric/lambda_literals.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | lambda_warning = "LAMBDA_WARNING" 6 | 7 | # Holds warning message that is emitted when an unhandled exception is raised during function invocation. 8 | lambda_unhandled_exception_warning_message = str( 9 | f"{lambda_warning}: " 10 | "Unhandled exception. " 11 | "The most likely cause is an issue in the function code. " 12 | "However, in rare cases, a Lambda runtime update can cause unexpected function behavior. " 13 | "For functions using managed runtimes, runtime updates can be triggered by a function change, or can be applied automatically. " 14 | "To determine if the runtime has been updated, check the runtime version in the INIT_START log entry. " 15 | "If this error correlates with a change in the runtime version, you may be able to mitigate this error by temporarily rolling back to the previous runtime version. " 16 | "For more information, see https://docs.aws.amazon.com/lambda/latest/dg/runtimes-update.html\r" 17 | ) 18 | -------------------------------------------------------------------------------- /awslambdaric/lambda_runtime_client.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | import sys 6 | from awslambdaric import __version__ 7 | from .lambda_runtime_exception import FaultException 8 | from .lambda_runtime_marshaller import to_json 9 | 10 | ERROR_TYPE_HEADER = "Lambda-Runtime-Function-Error-Type" 11 | 12 | 13 | def _user_agent(): 14 | py_version = ( 15 | f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" 16 | ) 17 | pkg_version = __version__ 18 | return f"aws-lambda-python/{py_version}-{pkg_version}" 19 | 20 | 21 | try: 22 | import runtime_client 23 | 24 | runtime_client.initialize_client(_user_agent()) 25 | except ImportError: 26 | runtime_client = None 27 | 28 | from .lambda_runtime_marshaller import LambdaMarshaller 29 | 30 | 31 | class InvocationRequest(object): 32 | def __init__(self, **kwds): 33 | self.__dict__.update(kwds) 34 | 35 | def __eq__(self, other): 36 | return self.__dict__ == other.__dict__ 37 | 38 | 39 | class LambdaRuntimeClientError(Exception): 40 | def __init__(self, endpoint, response_code, response_body): 41 | self.endpoint = endpoint 42 | self.response_code = response_code 43 | self.response_body = response_body 44 | super().__init__( 45 | f"Request to Lambda Runtime '{endpoint}' endpoint failed. Reason: '{response_code}'. Response body: '{response_body}'" 46 | ) 47 | 48 | 49 | class LambdaRuntimeClient(object): 50 | marshaller = LambdaMarshaller() 51 | """marshaller is a class attribute that determines the unmarshalling and marshalling logic of a function's event 52 | and response. It allows for function authors to override the the default implementation, LambdaMarshaller which 53 | unmarshals and marshals JSON, to an instance of a class that implements the same interface.""" 54 | 55 | def __init__(self, lambda_runtime_address, use_thread_for_polling_next=False): 56 | self.lambda_runtime_address = lambda_runtime_address 57 | self.use_thread_for_polling_next = use_thread_for_polling_next 58 | if self.use_thread_for_polling_next: 59 | # Conditionally import only for the case when TPE is used in this class. 60 | from concurrent.futures import ThreadPoolExecutor 61 | 62 | # Not defining symbol as global to avoid relying on TPE being imported unconditionally. 63 | self.ThreadPoolExecutor = ThreadPoolExecutor 64 | 65 | def call_rapid( 66 | self, http_method, endpoint, expected_http_code, payload=None, headers=None 67 | ): 68 | # These imports are heavy-weight. They implicitly trigger `import ssl, hashlib`. 69 | # Importing them lazily to speed up critical path of a common case. 70 | import http.client 71 | 72 | runtime_connection = http.client.HTTPConnection(self.lambda_runtime_address) 73 | runtime_connection.connect() 74 | if http_method == "GET": 75 | runtime_connection.request(http_method, endpoint) 76 | else: 77 | runtime_connection.request( 78 | http_method, endpoint, to_json(payload), headers=headers 79 | ) 80 | 81 | response = runtime_connection.getresponse() 82 | response_body = response.read() 83 | if response.code != expected_http_code: 84 | raise LambdaRuntimeClientError(endpoint, response.code, response_body) 85 | 86 | def post_init_error(self, error_response_data, error_type_override=None): 87 | import http 88 | 89 | endpoint = "/2018-06-01/runtime/init/error" 90 | headers = { 91 | ERROR_TYPE_HEADER: ( 92 | error_type_override 93 | if error_type_override 94 | else error_response_data["errorType"] 95 | ) 96 | } 97 | self.call_rapid( 98 | "POST", endpoint, http.HTTPStatus.ACCEPTED, error_response_data, headers 99 | ) 100 | 101 | def restore_next(self): 102 | import http 103 | 104 | endpoint = "/2018-06-01/runtime/restore/next" 105 | self.call_rapid("GET", endpoint, http.HTTPStatus.OK) 106 | 107 | def report_restore_error(self, restore_error_data): 108 | import http 109 | 110 | endpoint = "/2018-06-01/runtime/restore/error" 111 | headers = {ERROR_TYPE_HEADER: FaultException.AFTER_RESTORE_ERROR} 112 | self.call_rapid( 113 | "POST", endpoint, http.HTTPStatus.ACCEPTED, restore_error_data, headers 114 | ) 115 | 116 | def wait_next_invocation(self): 117 | # Calling runtime_client.next() from a separate thread unblocks the main thread, 118 | # which can then process signals. 119 | if self.use_thread_for_polling_next: 120 | try: 121 | # TPE class is supposed to be registered at construction time and be ready to use. 122 | with self.ThreadPoolExecutor(max_workers=1) as executor: 123 | future = executor.submit(runtime_client.next) 124 | response_body, headers = future.result() 125 | except Exception as e: 126 | raise FaultException( 127 | FaultException.LAMBDA_RUNTIME_CLIENT_ERROR, 128 | "LAMBDA_RUNTIME Failed to get next invocation: {}".format(str(e)), 129 | None, 130 | ) 131 | else: 132 | response_body, headers = runtime_client.next() 133 | return InvocationRequest( 134 | invoke_id=headers.get("Lambda-Runtime-Aws-Request-Id"), 135 | x_amzn_trace_id=headers.get("Lambda-Runtime-Trace-Id"), 136 | invoked_function_arn=headers.get("Lambda-Runtime-Invoked-Function-Arn"), 137 | deadline_time_in_ms=headers.get("Lambda-Runtime-Deadline-Ms"), 138 | client_context=headers.get("Lambda-Runtime-Client-Context"), 139 | cognito_identity=headers.get("Lambda-Runtime-Cognito-Identity"), 140 | tenant_id=headers.get("Lambda-Runtime-Aws-Tenant-Id"), 141 | content_type=headers.get("Content-Type"), 142 | event_body=response_body, 143 | ) 144 | 145 | def post_invocation_result( 146 | self, invoke_id, result_data, content_type="application/json" 147 | ): 148 | runtime_client.post_invocation_result( 149 | invoke_id, 150 | ( 151 | result_data 152 | if isinstance(result_data, bytes) 153 | else result_data.encode("utf-8") 154 | ), 155 | content_type, 156 | ) 157 | 158 | def post_invocation_error(self, invoke_id, error_response_data, xray_fault): 159 | max_header_size = 1024 * 1024 # 1MiB 160 | xray_fault = xray_fault if len(xray_fault.encode()) < max_header_size else "" 161 | runtime_client.post_error(invoke_id, error_response_data, xray_fault) 162 | -------------------------------------------------------------------------------- /awslambdaric/lambda_runtime_exception.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | 6 | class FaultException(Exception): 7 | MARSHAL_ERROR = "Runtime.MarshalError" 8 | UNMARSHAL_ERROR = "Runtime.UnmarshalError" 9 | USER_CODE_SYNTAX_ERROR = "Runtime.UserCodeSyntaxError" 10 | HANDLER_NOT_FOUND = "Runtime.HandlerNotFound" 11 | IMPORT_MODULE_ERROR = "Runtime.ImportModuleError" 12 | BUILT_IN_MODULE_CONFLICT = "Runtime.BuiltInModuleConflict" 13 | MALFORMED_HANDLER_NAME = "Runtime.MalformedHandlerName" 14 | BEFORE_SNAPSHOT_ERROR = "Runtime.BeforeSnapshotError" 15 | AFTER_RESTORE_ERROR = "Runtime.AfterRestoreError" 16 | LAMBDA_CONTEXT_UNMARSHAL_ERROR = "Runtime.LambdaContextUnmarshalError" 17 | LAMBDA_RUNTIME_CLIENT_ERROR = "Runtime.LambdaRuntimeClientError" 18 | 19 | def __init__(self, exception_type, msg, trace=None): 20 | self.msg = msg 21 | self.exception_type = exception_type 22 | self.trace = trace 23 | -------------------------------------------------------------------------------- /awslambdaric/lambda_runtime_hooks_runner.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from snapshot_restore_py import get_before_snapshot, get_after_restore 5 | 6 | 7 | def run_before_snapshot(): 8 | before_snapshot_callables = get_before_snapshot() 9 | while before_snapshot_callables: 10 | # Using pop as before checkpoint callables are executed in the reverse order of their registration 11 | func, args, kwargs = before_snapshot_callables.pop() 12 | func(*args, **kwargs) 13 | 14 | 15 | def run_after_restore(): 16 | after_restore_callables = get_after_restore() 17 | for func, args, kwargs in after_restore_callables: 18 | func(*args, **kwargs) 19 | -------------------------------------------------------------------------------- /awslambdaric/lambda_runtime_log_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | import json 6 | import logging 7 | import traceback 8 | from enum import IntEnum 9 | 10 | _DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" 11 | _RESERVED_FIELDS = { 12 | "name", 13 | "msg", 14 | "args", 15 | "levelname", 16 | "levelno", 17 | "pathname", 18 | "filename", 19 | "module", 20 | "exc_info", 21 | "exc_text", 22 | "stack_info", 23 | "lineno", 24 | "funcName", 25 | "created", 26 | "msecs", 27 | "relativeCreated", 28 | "thread", 29 | "threadName", 30 | "processName", 31 | "process", 32 | "aws_request_id", 33 | "tenant_id", 34 | "_frame_type", 35 | } 36 | 37 | 38 | class LogFormat(IntEnum): 39 | JSON = 0b0 40 | TEXT = 0b1 41 | 42 | @classmethod 43 | def from_str(cls, value: str): 44 | if value and value.upper() == "JSON": 45 | return cls.JSON.value 46 | return cls.TEXT.value 47 | 48 | 49 | def _get_log_level_from_env_var(log_level): 50 | return {None: "", "TRACE": "DEBUG"}.get(log_level, log_level).upper() 51 | 52 | 53 | _JSON_FRAME_TYPES = { 54 | logging.NOTSET: 0xA55A0002.to_bytes(4, "big"), 55 | logging.DEBUG: 0xA55A000A.to_bytes(4, "big"), 56 | logging.INFO: 0xA55A000E.to_bytes(4, "big"), 57 | logging.WARNING: 0xA55A0012.to_bytes(4, "big"), 58 | logging.ERROR: 0xA55A0016.to_bytes(4, "big"), 59 | logging.CRITICAL: 0xA55A001A.to_bytes(4, "big"), 60 | } 61 | _TEXT_FRAME_TYPES = { 62 | logging.NOTSET: 0xA55A0003.to_bytes(4, "big"), 63 | logging.DEBUG: 0xA55A000B.to_bytes(4, "big"), 64 | logging.INFO: 0xA55A000F.to_bytes(4, "big"), 65 | logging.WARNING: 0xA55A0013.to_bytes(4, "big"), 66 | logging.ERROR: 0xA55A0017.to_bytes(4, "big"), 67 | logging.CRITICAL: 0xA55A001B.to_bytes(4, "big"), 68 | } 69 | _DEFAULT_FRAME_TYPE = _TEXT_FRAME_TYPES[logging.NOTSET] 70 | 71 | _json_encoder = json.JSONEncoder(ensure_ascii=False) 72 | _encode_json = _json_encoder.encode 73 | 74 | 75 | def _format_log_level(record: logging.LogRecord) -> int: 76 | return min(50, max(0, record.levelno)) // 10 * 10 77 | 78 | 79 | class JsonFormatter(logging.Formatter): 80 | def __init__(self): 81 | super().__init__(datefmt=_DATETIME_FORMAT) 82 | 83 | @staticmethod 84 | def __format_stacktrace(exc_info): 85 | if not exc_info: 86 | return None 87 | return traceback.format_tb(exc_info[2]) 88 | 89 | @staticmethod 90 | def __format_exception_name(exc_info): 91 | if not exc_info: 92 | return None 93 | 94 | return exc_info[0].__name__ 95 | 96 | @staticmethod 97 | def __format_exception(exc_info): 98 | if not exc_info: 99 | return None 100 | 101 | return str(exc_info[1]) 102 | 103 | @staticmethod 104 | def __format_location(record: logging.LogRecord): 105 | if not record.exc_info: 106 | return None 107 | 108 | return f"{record.pathname}:{record.funcName}:{record.lineno}" 109 | 110 | def format(self, record: logging.LogRecord) -> str: 111 | record.levelno = _format_log_level(record) 112 | record.levelname = logging.getLevelName(record.levelno) 113 | record._frame_type = _JSON_FRAME_TYPES.get( 114 | record.levelno, _JSON_FRAME_TYPES[logging.NOTSET] 115 | ) 116 | 117 | result = { 118 | "timestamp": self.formatTime(record, self.datefmt), 119 | "level": record.levelname, 120 | "message": record.getMessage(), 121 | "logger": record.name, 122 | "stackTrace": self.__format_stacktrace(record.exc_info), 123 | "errorType": self.__format_exception_name(record.exc_info), 124 | "errorMessage": self.__format_exception(record.exc_info), 125 | "requestId": getattr(record, "aws_request_id", None), 126 | "location": self.__format_location(record), 127 | } 128 | if hasattr(record, "tenant_id") and record.tenant_id is not None: 129 | result["tenantId"] = record.tenant_id 130 | 131 | result.update( 132 | (key, value) 133 | for key, value in record.__dict__.items() 134 | if key not in _RESERVED_FIELDS and key not in result 135 | ) 136 | 137 | result = {k: v for k, v in result.items() if v is not None} 138 | 139 | return _encode_json(result) + "\n" 140 | -------------------------------------------------------------------------------- /awslambdaric/lambda_runtime_marshaller.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | import decimal 6 | import math 7 | import os 8 | import simplejson as json 9 | 10 | from .lambda_runtime_exception import FaultException 11 | 12 | 13 | # simplejson's Decimal encoding allows '-NaN' as an output, which is a parse error for json.loads 14 | # to get the good parts of Decimal support, we'll special-case NaN decimals and otherwise duplicate the encoding for decimals the same way simplejson does 15 | # We also set 'ensure_ascii=False' so that the encoded json contains unicode characters instead of unicode escape sequences 16 | class Encoder(json.JSONEncoder): 17 | def __init__(self): 18 | if os.environ.get("AWS_EXECUTION_ENV") in { 19 | "AWS_Lambda_python3.12", 20 | "AWS_Lambda_python3.13", 21 | }: 22 | super().__init__(use_decimal=False, ensure_ascii=False, allow_nan=True) 23 | else: 24 | super().__init__(use_decimal=False, allow_nan=True) 25 | 26 | def default(self, obj): 27 | if isinstance(obj, decimal.Decimal): 28 | if obj.is_nan(): 29 | return math.nan 30 | return json.raw_json.RawJSON(str(obj)) 31 | return super().default(obj) 32 | 33 | 34 | def to_json(obj): 35 | return Encoder().encode(obj) 36 | 37 | 38 | class LambdaMarshaller: 39 | def __init__(self): 40 | self.jsonEncoder = Encoder() 41 | 42 | def unmarshal_request(self, request, content_type="application/json"): 43 | if content_type != "application/json": 44 | return request 45 | try: 46 | return json.loads(request) 47 | except Exception as e: 48 | raise FaultException( 49 | FaultException.UNMARSHAL_ERROR, 50 | "Unable to unmarshal input: {}".format(str(e)), 51 | None, 52 | ) 53 | 54 | def marshal_response(self, response): 55 | if isinstance(response, bytes): 56 | return response, "application/unknown" 57 | 58 | try: 59 | return self.jsonEncoder.encode(response), "application/json" 60 | except Exception as e: 61 | raise FaultException( 62 | FaultException.MARSHAL_ERROR, 63 | "Unable to marshal response: {}".format(str(e)), 64 | None, 65 | ) 66 | -------------------------------------------------------------------------------- /awslambdaric/runtime_client.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define NULL_IF_EMPTY(v) (((v) == NULL || (v)[0] == 0) ? NULL : (v)) 9 | 10 | static const std::string ENDPOINT(getenv("AWS_LAMBDA_RUNTIME_API") ? getenv("AWS_LAMBDA_RUNTIME_API") : "127.0.0.1:9001"); 11 | static aws::lambda_runtime::runtime *CLIENT; 12 | 13 | static PyObject *method_initialize_client(PyObject *self, PyObject *args) { 14 | char *user_agent_arg; 15 | if (!PyArg_ParseTuple(args, "s", &user_agent_arg)) { 16 | PyErr_SetString(PyExc_RuntimeError, "Wrong arguments"); 17 | return NULL; 18 | } 19 | 20 | const std::string user_agent = std::string(user_agent_arg); 21 | 22 | CLIENT = new aws::lambda_runtime::runtime(ENDPOINT, user_agent); 23 | Py_INCREF(Py_None); 24 | return Py_None; 25 | } 26 | 27 | static PyObject *method_next(PyObject *self) { 28 | aws::lambda_runtime::invocation_request response; 29 | 30 | // Release GIL and save thread state 31 | // ref: https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock 32 | PyThreadState *_save; 33 | _save = PyEval_SaveThread(); 34 | 35 | auto outcome = CLIENT->get_next(); 36 | if (!outcome.is_success()) { 37 | // Reacquire GIL before exiting 38 | PyEval_RestoreThread(_save); 39 | PyErr_SetString(PyExc_RuntimeError, "Failed to get next"); 40 | return NULL; 41 | } 42 | 43 | response = outcome.get_result(); 44 | // Reacquire GIL before constructing return object 45 | PyEval_RestoreThread(_save); 46 | 47 | auto payload = response.payload; 48 | auto request_id = response.request_id.c_str(); 49 | auto trace_id = response.xray_trace_id.c_str(); 50 | auto function_arn = response.function_arn.c_str(); 51 | auto deadline = std::chrono::duration_cast(response.deadline.time_since_epoch()).count(); 52 | auto client_context = response.client_context.c_str(); 53 | auto content_type = response.content_type.c_str(); 54 | auto cognito_id = response.cognito_identity.c_str(); 55 | auto tenant_id = response.tenant_id.c_str(); 56 | 57 | PyObject *payload_bytes = PyBytes_FromStringAndSize(payload.c_str(), payload.length()); 58 | PyObject *result = Py_BuildValue("(O,{s:s,s:s,s:s,s:l,s:s,s:s,s:s,s:s})", 59 | payload_bytes, //Py_BuildValue() increments reference counter 60 | "Lambda-Runtime-Aws-Request-Id", request_id, 61 | "Lambda-Runtime-Trace-Id", NULL_IF_EMPTY(trace_id), 62 | "Lambda-Runtime-Invoked-Function-Arn", function_arn, 63 | "Lambda-Runtime-Deadline-Ms", deadline, 64 | "Lambda-Runtime-Client-Context", NULL_IF_EMPTY(client_context), 65 | "Content-Type", NULL_IF_EMPTY(content_type), 66 | "Lambda-Runtime-Cognito-Identity", NULL_IF_EMPTY(cognito_id), 67 | "Lambda-Runtime-Aws-Tenant-Id", NULL_IF_EMPTY(tenant_id) 68 | ); 69 | 70 | Py_XDECREF(payload_bytes); 71 | return result; 72 | } 73 | 74 | static PyObject *method_post_invocation_result(PyObject *self, PyObject *args) { 75 | if (CLIENT == nullptr) { 76 | PyErr_SetString(PyExc_RuntimeError, "Client not yet initalized"); 77 | return NULL; 78 | } 79 | 80 | PyObject *invocation_response; 81 | Py_ssize_t length; 82 | char *request_id, *content_type, *response_as_c_string; 83 | 84 | if (!PyArg_ParseTuple(args, "sSs", &request_id, &invocation_response, &content_type)) { 85 | PyErr_SetString(PyExc_RuntimeError, "Wrong arguments"); 86 | return NULL; 87 | } 88 | 89 | length = PyBytes_Size(invocation_response); 90 | response_as_c_string = PyBytes_AsString(invocation_response); 91 | std::string response_string(response_as_c_string, response_as_c_string + length); 92 | 93 | auto response = aws::lambda_runtime::invocation_response::success(response_string, content_type); 94 | auto outcome = CLIENT->post_success(request_id, response); 95 | if (!outcome.is_success()) { 96 | PyErr_SetString(PyExc_RuntimeError, "Failed to post invocation response"); 97 | return NULL; 98 | } 99 | 100 | Py_INCREF(Py_None); 101 | return Py_None; 102 | } 103 | 104 | static PyObject *method_post_error(PyObject *self, PyObject *args) { 105 | if (CLIENT == nullptr) { 106 | PyErr_SetString(PyExc_RuntimeError, "Client not yet initalized"); 107 | return NULL; 108 | } 109 | 110 | char *request_id, *response_string, *xray_fault; 111 | 112 | if (!PyArg_ParseTuple(args, "sss", &request_id, &response_string, &xray_fault)) { 113 | PyErr_SetString(PyExc_RuntimeError, "Wrong arguments"); 114 | return NULL; 115 | } 116 | 117 | auto response = aws::lambda_runtime::invocation_response(response_string, "application/json", false, xray_fault); 118 | auto outcome = CLIENT->post_failure(request_id, response); 119 | if (!outcome.is_success()) { 120 | PyErr_SetString(PyExc_RuntimeError, "Failed to post invocation error"); 121 | return NULL; 122 | } 123 | 124 | Py_INCREF(Py_None); 125 | return Py_None; 126 | } 127 | 128 | static PyMethodDef Runtime_Methods[] = { 129 | {"initialize_client", method_initialize_client, METH_VARARGS, NULL}, 130 | {"next", (PyCFunction) method_next, METH_NOARGS, NULL}, 131 | {"post_invocation_result", method_post_invocation_result, METH_VARARGS, NULL}, 132 | {"post_error", method_post_error, METH_VARARGS, NULL}, 133 | {NULL, NULL, 0, NULL} 134 | }; 135 | 136 | static struct PyModuleDef runtime_client = { 137 | PyModuleDef_HEAD_INIT, 138 | "runtime", 139 | NULL, 140 | -1, 141 | Runtime_Methods, 142 | NULL, 143 | NULL, 144 | NULL, 145 | NULL 146 | }; 147 | 148 | PyMODINIT_FUNC PyInit_runtime_client(void) { 149 | return PyModule_Create(&runtime_client); 150 | } 151 | -------------------------------------------------------------------------------- /deps/aws-lambda-cpp-0.2.6.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-lambda-python-runtime-interface-client/3f43f4d089600669435038d7e1fa41145d9ff94f/deps/aws-lambda-cpp-0.2.6.tar.gz -------------------------------------------------------------------------------- /deps/curl-7.83.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-lambda-python-runtime-interface-client/3f43f4d089600669435038d7e1fa41145d9ff94f/deps/curl-7.83.1.tar.gz -------------------------------------------------------------------------------- /deps/patches/aws-lambda-cpp-add-content-type.patch: -------------------------------------------------------------------------------- 1 | diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h 2 | index e14b804..cc1a789 100644 3 | --- a/include/aws/lambda-runtime/runtime.h 4 | +++ b/include/aws/lambda-runtime/runtime.h 5 | @@ -56,6 +56,11 @@ struct invocation_request { 6 | */ 7 | std::string function_arn; 8 | 9 | + /** 10 | + * The Content-type of the current invocation. 11 | + */ 12 | + std::string content_type; 13 | + 14 | /** 15 | * Function execution deadline counted in milliseconds since the Unix epoch. 16 | */ 17 | diff --git a/include/aws/http/response.h b/include/aws/http/response.h 18 | index 9b8cbda..be184c1 100644 19 | --- a/include/aws/http/response.h 20 | +++ b/include/aws/http/response.h 21 | @@ -36,6 +36,7 @@ public: 22 | inline void set_response_code(aws::http::response_code c); 23 | inline void set_content_type(char const* ct); 24 | inline std::string const& get_body() const; 25 | + inline std::string const& get_content_type() const; 26 | 27 | private: 28 | response_code m_response_code; 29 | @@ -137,6 +138,12 @@ inline std::string const& response::get_body() const 30 | { 31 | return m_body; 32 | } 33 | + 34 | +inline std::string const& response::get_content_type() const 35 | +{ 36 | + return m_content_type; 37 | +} 38 | + 39 | inline void response::add_header(std::string name, std::string const& value) 40 | { 41 | std::transform(name.begin(), name.end(), name.begin(), ::tolower); 42 | diff --git a/src/runtime.cpp b/src/runtime.cpp 43 | index 08d7014..1cbd6bb 100644 44 | --- a/src/runtime.cpp 45 | +++ b/src/runtime.cpp 46 | @@ -275,6 +275,7 @@ runtime::next_outcome runtime::get_next() 47 | invocation_request req; 48 | req.payload = resp.get_body(); 49 | req.request_id = resp.get_header(REQUEST_ID_HEADER); 50 | + req.content_type = resp.get_content_type(); 51 | 52 | if (resp.has_header(TRACE_ID_HEADER)) { 53 | req.xray_trace_id = resp.get_header(TRACE_ID_HEADER); 54 | -------------------------------------------------------------------------------- /deps/patches/aws-lambda-cpp-add-tenant-id.patch: -------------------------------------------------------------------------------- 1 | diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h 2 | index 7812ff6..96be869 100644 3 | --- a/include/aws/lambda-runtime/runtime.h 4 | +++ b/include/aws/lambda-runtime/runtime.h 5 | @@ -61,6 +61,11 @@ struct invocation_request { 6 | */ 7 | std::string content_type; 8 | 9 | + /** 10 | + * The Tenant ID of the current invocation. 11 | + */ 12 | + std::string tenant_id; 13 | + 14 | /** 15 | * Function execution deadline counted in milliseconds since the Unix epoch. 16 | */ 17 | diff --git a/src/runtime.cpp b/src/runtime.cpp 18 | index e53b2b8..9763282 100644 19 | --- a/src/runtime.cpp 20 | +++ b/src/runtime.cpp 21 | @@ -40,6 +40,7 @@ static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context"; 22 | static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity"; 23 | static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms"; 24 | static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn"; 25 | +static constexpr auto TENANT_ID_HEADER = "lambda-runtime-aws-tenant-id"; 26 | 27 | enum Endpoints { 28 | INIT, 29 | @@ -289,6 +290,10 @@ runtime::next_outcome runtime::get_next() 30 | req.function_arn = resp.get_header(FUNCTION_ARN_HEADER); 31 | } 32 | 33 | + if (resp.has_header(TENANT_ID_HEADER)) { 34 | + req.tenant_id = resp.get_header(TENANT_ID_HEADER); 35 | + } 36 | + 37 | if (resp.has_header(DEADLINE_MS_HEADER)) { 38 | auto const& deadline_string = resp.get_header(DEADLINE_MS_HEADER); 39 | constexpr int base = 10; 40 | -------------------------------------------------------------------------------- /deps/patches/aws-lambda-cpp-add-xray-response.patch: -------------------------------------------------------------------------------- 1 | diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h 2 | index 0dc292c..be77d93 100644 3 | --- a/include/aws/lambda-runtime/runtime.h 4 | +++ b/include/aws/lambda-runtime/runtime.h 5 | @@ -85,6 +85,11 @@ private: 6 | */ 7 | bool m_success; 8 | 9 | + /** 10 | + * The serialized XRay response header. 11 | + */ 12 | + std::string m_xray_response; 13 | + 14 | /** 15 | * Instantiate an empty response. Used by the static functions 'success' and 'failure' to create a populated 16 | * invocation_response 17 | @@ -97,10 +102,12 @@ public: 18 | // To support clients that need to control the entire error response body (e.g. adding a stack trace), this 19 | // constructor should be used instead. 20 | // Note: adding an overload to invocation_response::failure is not feasible since the parameter types are the same. 21 | - invocation_response(std::string const& payload, std::string const& content_type, bool success) 22 | - : m_payload(payload), m_content_type(content_type), m_success(success) 23 | - { 24 | - } 25 | + invocation_response(std::string const& payload, std::string const& content_type, bool success, std::string const& xray_response): 26 | + m_payload(payload), 27 | + m_content_type(content_type), 28 | + m_success(success), 29 | + m_xray_response(xray_response) 30 | + {} 31 | 32 | /** 33 | * Create a successful invocation response with the given payload and content-type. 34 | @@ -111,7 +118,7 @@ public: 35 | * Create a failure response with the given error message and error type. 36 | * The content-type is always set to application/json in this case. 37 | */ 38 | - static invocation_response failure(std::string const& error_message, std::string const& error_type); 39 | + static invocation_response failure(std::string const& error_message, std::string const& error_type, std::string const& xray_response); 40 | 41 | /** 42 | * Get the MIME type of the payload. 43 | @@ -127,6 +134,11 @@ public: 44 | * Returns true if the payload and content-type are set. Returns false if the error message and error types are set. 45 | */ 46 | bool is_success() const { return m_success; } 47 | + 48 | + /** 49 | + * Get the XRay response string. The string isassumed to be UTF-8 encoded. 50 | + */ 51 | + std::string const& get_xray_response() const { return m_xray_response; } 52 | }; 53 | 54 | struct no_result { 55 | diff --git a/src/runtime.cpp b/src/runtime.cpp 56 | index e2ee7cd..d895c4b 100644 57 | --- a/src/runtime.cpp 58 | +++ b/src/runtime.cpp 59 | @@ -337,6 +337,7 @@ runtime::post_outcome runtime::do_post( 60 | headers = curl_slist_append(headers, ("content-type: " + handler_response.get_content_type()).c_str()); 61 | } 62 | 63 | + headers = curl_slist_append(headers, ("lambda-runtime-function-xray-error-cause: " + handler_response.get_xray_response()).c_str()); 64 | headers = curl_slist_append(headers, "Expect:"); 65 | headers = curl_slist_append(headers, "transfer-encoding:"); 66 | headers = curl_slist_append(headers, get_user_agent_header().c_str()); 67 | @@ -511,13 +512,15 @@ invocation_response invocation_response::success(std::string const& payload, std 68 | } 69 | 70 | AWS_LAMBDA_RUNTIME_API 71 | -invocation_response invocation_response::failure(std::string const& error_message, std::string const& error_type) 72 | +invocation_response invocation_response::failure(std::string const& error_message, std::string const& error_type, std::string const& xray_response) 73 | { 74 | invocation_response r; 75 | r.m_success = false; 76 | r.m_content_type = "application/json"; 77 | r.m_payload = R"({"errorMessage":")" + json_escape(error_message) + R"(","errorType":")" + json_escape(error_type) + 78 | R"(", "stackTrace":[]})"; 79 | + r.m_xray_response = xray_response; 80 | + 81 | return r; 82 | } 83 | -------------------------------------------------------------------------------- /deps/patches/aws-lambda-cpp-make-lto-optional.patch: -------------------------------------------------------------------------------- 1 | diff --git a/CMakeLists.txt b/CMakeLists.txt 2 | index eb8327f..e6eeda5 100644 3 | --- a/CMakeLists.txt 4 | +++ b/CMakeLists.txt 5 | @@ -4,10 +4,9 @@ project(aws-lambda-runtime 6 | VERSION 0.2.6 7 | LANGUAGES CXX) 8 | 9 | +option(ENABLE_LTO "Enables link-time optimization, requires compiler support." ON) 10 | option(ENABLE_TESTS "Enables building the test project, requires AWS C++ SDK." OFF) 11 | 12 | -include(CheckIPOSupported) 13 | - 14 | add_library(${PROJECT_NAME} 15 | "src/logging.cpp" 16 | "src/runtime.cpp" 17 | @@ -23,11 +22,14 @@ target_include_directories(${PROJECT_NAME} PUBLIC 18 | $ 19 | $) 20 | 21 | -check_ipo_supported(RESULT has_lto OUTPUT lto_check_output) 22 | -if(has_lto) 23 | - set_property(TARGET ${PROJECT_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) 24 | -else() 25 | - message(WARNING "Link-time optimization (LTO) is not supported: ${lto_check_output}") 26 | +if (ENABLE_LTO) 27 | + include(CheckIPOSupported) 28 | + check_ipo_supported(RESULT has_lto OUTPUT lto_check_output) 29 | + if(has_lto) 30 | + set_property(TARGET ${PROJECT_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) 31 | + else() 32 | + message(WARNING "Link-time optimization (LTO) is not supported: ${lto_check_output}") 33 | + endif() 34 | endif() 35 | 36 | find_package(CURL REQUIRED) 37 | -------------------------------------------------------------------------------- /deps/patches/aws-lambda-cpp-make-the-runtime-client-user-agent-overrideable.patch: -------------------------------------------------------------------------------- 1 | From 846392515b2b0215902aaf7368651af196835f10 Mon Sep 17 00:00:00 2001 2 | From: Bryan Moffatt 3 | Date: Wed, 21 Oct 2020 12:42:37 -0700 4 | Subject: [PATCH] make the Runtime Interface Client's user agent overrideable (#106) 5 | 6 | * make the Runtime Interface Client's user agent overrideable 7 | 8 | * remove extra empty string 9 | 10 | * clang-format -i src/* 11 | --- 12 | include/aws/lambda-runtime/runtime.h | 2 ++ 13 | src/runtime.cpp | 20 ++++++++------------ 14 | 2 files changed, 10 insertions(+), 12 deletions(-) 15 | 16 | diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h 17 | index 0dc292c..94e1e22 100644 18 | --- a/include/aws/lambda-runtime/runtime.h 19 | +++ b/include/aws/lambda-runtime/runtime.h 20 | @@ -137,6 +137,7 @@ public: 21 | using next_outcome = aws::lambda_runtime::outcome; 22 | using post_outcome = aws::lambda_runtime::outcome; 23 | 24 | + runtime(std::string const& endpoint, std::string const& user_agent); 25 | runtime(std::string const& endpoint); 26 | ~runtime(); 27 | 28 | @@ -164,6 +165,7 @@ private: 29 | invocation_response const& handler_response); 30 | 31 | private: 32 | + std::string const m_user_agent_header; 33 | std::array const m_endpoints; 34 | CURL* const m_curl_handle; 35 | }; 36 | diff --git a/src/runtime.cpp b/src/runtime.cpp 37 | index e2ee7cd..f6131a4 100644 38 | --- a/src/runtime.cpp 39 | +++ b/src/runtime.cpp 40 | @@ -124,12 +124,6 @@ static size_t write_header(char* ptr, size_t size, size_t nmemb, void* userdata) 41 | return size * nmemb; 42 | } 43 | 44 | -static std::string const& get_user_agent_header() 45 | -{ 46 | - static std::string user_agent = std::string("User-Agent: AWS_Lambda_Cpp/") + get_version(); 47 | - return user_agent; 48 | -} 49 | - 50 | static size_t read_data(char* buffer, size_t size, size_t nitems, void* userdata) 51 | { 52 | auto const limit = size * nitems; 53 | @@ -163,10 +157,12 @@ static int rt_curl_debug_callback(CURL* handle, curl_infotype type, char* data, 54 | } 55 | #endif 56 | 57 | -runtime::runtime(std::string const& endpoint) 58 | - : m_endpoints{{endpoint + "/2018-06-01/runtime/init/error", 59 | - endpoint + "/2018-06-01/runtime/invocation/next", 60 | - endpoint + "/2018-06-01/runtime/invocation/"}}, 61 | +runtime::runtime(std::string const& endpoint) : runtime(endpoint, "AWS_Lambda_Cpp/" + std::string(get_version())) {} 62 | + 63 | +runtime::runtime(std::string const& endpoint, std::string const& user_agent) 64 | + : m_user_agent_header("User-Agent: " + user_agent), m_endpoints{{endpoint + "/2018-06-01/runtime/init/error", 65 | + endpoint + "/2018-06-01/runtime/invocation/next", 66 | + endpoint + "/2018-06-01/runtime/invocation/"}}, 67 | m_curl_handle(curl_easy_init()) 68 | { 69 | if (!m_curl_handle) { 70 | @@ -234,7 +230,7 @@ runtime::next_outcome runtime::get_next() 71 | curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, &resp); 72 | 73 | curl_slist* headers = nullptr; 74 | - headers = curl_slist_append(headers, get_user_agent_header().c_str()); 75 | + headers = curl_slist_append(headers, m_user_agent_header.c_str()); 76 | curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, headers); 77 | 78 | logging::log_debug(LOG_TAG, "Making request to %s", m_endpoints[Endpoints::NEXT].c_str()); 79 | @@ -343,7 +343,7 @@ runtime::post_outcome runtime::do_post( 80 | headers = curl_slist_append(headers, ("lambda-runtime-function-xray-error-cause: " + xray_response).c_str()); 81 | headers = curl_slist_append(headers, "Expect:"); 82 | headers = curl_slist_append(headers, "transfer-encoding:"); 83 | - headers = curl_slist_append(headers, get_user_agent_header().c_str()); 84 | + headers = curl_slist_append(headers, m_user_agent_header.c_str()); 85 | 86 | logging::log_debug( 87 | LOG_TAG, "calculating content length... %s", ("content-length: " + std::to_string(payload.length())).c_str()); 88 | -- 89 | 2.25.2 90 | 91 | -------------------------------------------------------------------------------- /deps/patches/aws-lambda-cpp-posting-init-errors.patch: -------------------------------------------------------------------------------- 1 | diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h 2 | index be77d93..9597272 100644 3 | --- a/include/aws/lambda-runtime/runtime.h 4 | +++ b/include/aws/lambda-runtime/runtime.h 5 | @@ -67,28 +67,58 @@ struct invocation_request { 6 | inline std::chrono::milliseconds get_time_remaining() const; 7 | }; 8 | 9 | -class invocation_response { 10 | -private: 11 | +class runtime_response { 12 | +protected: 13 | /** 14 | - * The output of the function which is sent to the lambda caller. 15 | + * The response payload from the runtime. 16 | */ 17 | std::string m_payload; 18 | 19 | /** 20 | * The MIME type of the payload. 21 | - * This is always set to 'application/json' in unsuccessful invocations. 22 | */ 23 | std::string m_content_type; 24 | 25 | /** 26 | - * Flag to distinguish if the contents are for successful or unsuccessful invocations. 27 | + * The serialized XRay response header. 28 | */ 29 | - bool m_success; 30 | + std::string m_xray_response; 31 | 32 | /** 33 | - * The serialized XRay response header. 34 | + * Instantiate an empty response. 35 | */ 36 | - std::string m_xray_response; 37 | + runtime_response() = default; 38 | +public: 39 | + /* Create a runtime response with the given payload, content type and xray response. This can be used for constructing an 40 | + * initialization error response. For invocation success and failure response, see invocation_response. 41 | + */ 42 | + runtime_response(std::string const& payload, std::string const& content_type, std::string const& xray_response) 43 | + : m_payload(payload), m_content_type(content_type), m_xray_response(xray_response) 44 | + { 45 | + } 46 | + 47 | + /** 48 | + * Get the payload string. The string is assumed to be UTF-8 encoded. 49 | + */ 50 | + std::string const& get_payload() const { return m_payload; } 51 | + 52 | + /** 53 | + * Get the MIME type of the payload. 54 | + */ 55 | + std::string const& get_content_type() const { return m_content_type; } 56 | + 57 | + /** 58 | + * Get the XRay response string. The string is assumed to be UTF-8 encoded. 59 | + */ 60 | + std::string const& get_xray_response() const { return m_xray_response; } 61 | +}; 62 | + 63 | +class invocation_response: public runtime_response { 64 | +private: 65 | + /** 66 | + * Flag to distinguish if the contents are for successful or unsuccessful invocations. 67 | + */ 68 | + bool m_success; 69 | 70 | /** 71 | * Instantiate an empty response. Used by the static functions 'success' and 'failure' to create a populated 72 | @@ -102,12 +132,10 @@ public: 73 | // To support clients that need to control the entire error response body (e.g. adding a stack trace), this 74 | // constructor should be used instead. 75 | // Note: adding an overload to invocation_response::failure is not feasible since the parameter types are the same. 76 | - invocation_response(std::string const& payload, std::string const& content_type, bool success, std::string const& xray_response): 77 | - m_payload(payload), 78 | - m_content_type(content_type), 79 | - m_success(success), 80 | - m_xray_response(xray_response) 81 | - {} 82 | + invocation_response(std::string const& payload, std::string const& content_type, bool success, std::string const& xray_response) 83 | + : runtime_response(payload, content_type, xray_response), m_success(success) 84 | + { 85 | + } 86 | 87 | /** 88 | * Create a successful invocation response with the given payload and content-type. 89 | @@ -120,25 +148,10 @@ public: 90 | */ 91 | static invocation_response failure(std::string const& error_message, std::string const& error_type, std::string const& xray_response); 92 | 93 | - /** 94 | - * Get the MIME type of the payload. 95 | - */ 96 | - std::string const& get_content_type() const { return m_content_type; } 97 | - 98 | - /** 99 | - * Get the payload string. The string is assumed to be UTF-8 encoded. 100 | - */ 101 | - std::string const& get_payload() const { return m_payload; } 102 | - 103 | /** 104 | * Returns true if the payload and content-type are set. Returns false if the error message and error types are set. 105 | */ 106 | bool is_success() const { return m_success; } 107 | - 108 | - /** 109 | - * Get the XRay response string. The string isassumed to be UTF-8 encoded. 110 | - */ 111 | - std::string const& get_xray_response() const { return m_xray_response; } 112 | }; 113 | 114 | struct no_result { 115 | @@ -167,13 +180,19 @@ public: 116 | */ 117 | post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response); 118 | 119 | + /** 120 | + * Tells lambda that the runtime has failed during initialization. 121 | + */ 122 | + post_outcome post_init_error(runtime_response const& init_error_response); 123 | + 124 | private: 125 | void set_curl_next_options(); 126 | void set_curl_post_result_options(); 127 | post_outcome do_post( 128 | std::string const& url, 129 | - std::string const& request_id, 130 | - invocation_response const& handler_response); 131 | + std::string const& content_type, 132 | + std::string const& payload, 133 | + std::string const& xray_response); 134 | 135 | private: 136 | std::array const m_endpoints; 137 | diff --git a/src/runtime.cpp b/src/runtime.cpp 138 | index d895c4b..659666e 100644 139 | --- a/src/runtime.cpp 140 | +++ b/src/runtime.cpp 141 | @@ -311,37 +311,44 @@ runtime::next_outcome runtime::get_next() 142 | runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response) 143 | { 144 | std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/response"; 145 | - return do_post(url, request_id, handler_response); 146 | + return do_post(url, handler_response.get_content_type(), handler_response.get_payload(), handler_response.get_xray_response()); 147 | } 148 | 149 | runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response) 150 | { 151 | std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/error"; 152 | - return do_post(url, request_id, handler_response); 153 | + return do_post(url, handler_response.get_content_type(), handler_response.get_payload(), handler_response.get_xray_response()); 154 | +} 155 | + 156 | +runtime::post_outcome runtime::post_init_error(runtime_response const& init_error_response) 157 | +{ 158 | + std::string const url = m_endpoints[Endpoints::INIT]; 159 | + return do_post(url, init_error_response.get_content_type(), init_error_response.get_payload(), init_error_response.get_xray_response()); 160 | } 161 | 162 | runtime::post_outcome runtime::do_post( 163 | std::string const& url, 164 | - std::string const& request_id, 165 | - invocation_response const& handler_response) 166 | + std::string const& content_type, 167 | + std::string const& payload, 168 | + std::string const& xray_response) 169 | { 170 | set_curl_post_result_options(); 171 | curl_easy_setopt(m_curl_handle, CURLOPT_URL, url.c_str()); 172 | logging::log_info(LOG_TAG, "Making request to %s", url.c_str()); 173 | 174 | curl_slist* headers = nullptr; 175 | - if (handler_response.get_content_type().empty()) { 176 | + if (content_type.empty()) { 177 | headers = curl_slist_append(headers, "content-type: text/html"); 178 | } 179 | else { 180 | - headers = curl_slist_append(headers, ("content-type: " + handler_response.get_content_type()).c_str()); 181 | + headers = curl_slist_append(headers, ("content-type: " + content_type).c_str()); 182 | } 183 | 184 | - headers = curl_slist_append(headers, ("lambda-runtime-function-xray-error-cause: " + handler_response.get_xray_response()).c_str()); 185 | + headers = curl_slist_append(headers, ("lambda-runtime-function-xray-error-cause: " + xray_response).c_str()); 186 | headers = curl_slist_append(headers, "Expect:"); 187 | headers = curl_slist_append(headers, "transfer-encoding:"); 188 | headers = curl_slist_append(headers, get_user_agent_header().c_str()); 189 | - auto const& payload = handler_response.get_payload(); 190 | + 191 | logging::log_debug( 192 | LOG_TAG, "calculating content length... %s", ("content-length: " + std::to_string(payload.length())).c_str()); 193 | headers = curl_slist_append(headers, ("content-length: " + std::to_string(payload.length())).c_str()); 194 | @@ -358,10 +365,10 @@ runtime::post_outcome runtime::do_post( 195 | if (curl_code != CURLE_OK) { 196 | logging::log_debug( 197 | LOG_TAG, 198 | - "CURL returned error code %d - %s, for invocation %s", 199 | + "CURL returned error code %d - %s, when calling %s", 200 | curl_code, 201 | curl_easy_strerror(curl_code), 202 | - request_id.c_str()); 203 | + url.c_str()); 204 | return aws::http::response_code::REQUEST_NOT_MADE; 205 | } 206 | 207 | -------------------------------------------------------------------------------- /deps/patches/libcurl-configure-template.patch: -------------------------------------------------------------------------------- 1 | diff --git a/configure.ac b/configure.ac 2 | index d24daea..64aca7f 100644 3 | --- a/configure.ac 4 | +++ b/configure.ac 5 | @@ -193,87 +193,96 @@ AS_HELP_STRING([--with-schannel],[enable Windows native SSL/TLS]), 6 | 7 | OPT_SECURETRANSPORT=no 8 | AC_ARG_WITH(secure-transport,dnl 9 | -AS_HELP_STRING([--with-secure-transport],[enable Apple OS native SSL/TLS]), 10 | +AS_HELP_STRING([--with-secure-transport],[enable Apple OS native SSL/TLS]),[ 11 | OPT_SECURETRANSPORT=$withval 12 | test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }Secure-Transport" 13 | -) 14 | +]) 15 | 16 | OPT_AMISSL=no 17 | AC_ARG_WITH(amissl,dnl 18 | -AS_HELP_STRING([--with-amissl],[enable Amiga native SSL/TLS (AmiSSL)]), 19 | +AS_HELP_STRING([--with-amissl],[enable Amiga native SSL/TLS (AmiSSL)]),[ 20 | OPT_AMISSL=$withval 21 | - test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }AmiSSL") 22 | + test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }AmiSSL" 23 | +]) 24 | + 25 | 26 | OPT_OPENSSL=no 27 | dnl Default to no CA bundle 28 | ca="no" 29 | AC_ARG_WITH(ssl,dnl 30 | AS_HELP_STRING([--with-ssl=PATH],[old version of --with-openssl]) 31 | -AS_HELP_STRING([--without-ssl], [build without any TLS library]), 32 | +AS_HELP_STRING([--without-ssl], [build without any TLS library]),[ 33 | OPT_SSL=$withval 34 | OPT_OPENSSL=$withval 35 | if test X"$withval" != Xno; then 36 | - test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL") 37 | + test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL" 38 | fi 39 | +]) 40 | 41 | AC_ARG_WITH(openssl,dnl 42 | -AS_HELP_STRING([--with-openssl=PATH],[Where to look for OpenSSL, PATH points to the SSL installation (default: /usr/local/ssl); when possible, set the PKG_CONFIG_PATH environment variable instead of using this option]), 43 | +AS_HELP_STRING([--with-openssl=PATH],[Where to look for OpenSSL, PATH points to the SSL installation (default: /usr/local/ssl); when possible, set the PKG_CONFIG_PATH environment variable instead of using this option]),[ 44 | OPT_OPENSSL=$withval 45 | if test X"$withval" != Xno; then 46 | - test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL") 47 | + test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }OpenSSL" 48 | fi 49 | +]) 50 | 51 | OPT_GNUTLS=no 52 | AC_ARG_WITH(gnutls,dnl 53 | -AS_HELP_STRING([--with-gnutls=PATH],[where to look for GnuTLS, PATH points to the installation root]), 54 | +AS_HELP_STRING([--with-gnutls=PATH],[where to look for GnuTLS, PATH points to the installation root]),[ 55 | OPT_GNUTLS=$withval 56 | if test X"$withval" != Xno; then 57 | - test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }GnuTLS") 58 | + test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }GnuTLS" 59 | fi 60 | +]) 61 | 62 | OPT_MBEDTLS=no 63 | AC_ARG_WITH(mbedtls,dnl 64 | -AS_HELP_STRING([--with-mbedtls=PATH],[where to look for mbedTLS, PATH points to the installation root]), 65 | +AS_HELP_STRING([--with-mbedtls=PATH],[where to look for mbedTLS, PATH points to the installation root]),[ 66 | OPT_MBEDTLS=$withval 67 | if test X"$withval" != Xno; then 68 | - test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }mbedTLS") 69 | + test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }mbedTLS" 70 | fi 71 | +]) 72 | 73 | OPT_WOLFSSL=no 74 | AC_ARG_WITH(wolfssl,dnl 75 | -AS_HELP_STRING([--with-wolfssl=PATH],[where to look for WolfSSL, PATH points to the installation root (default: system lib default)]), 76 | +AS_HELP_STRING([--with-wolfssl=PATH],[where to look for WolfSSL, PATH points to the installation root (default: system lib default)]),[ 77 | OPT_WOLFSSL=$withval 78 | if test X"$withval" != Xno; then 79 | - test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }wolfSSL") 80 | + test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }wolfSSL" 81 | fi 82 | +]) 83 | 84 | OPT_BEARSSL=no 85 | AC_ARG_WITH(bearssl,dnl 86 | -AS_HELP_STRING([--with-bearssl=PATH],[where to look for BearSSL, PATH points to the installation root]), 87 | +AS_HELP_STRING([--with-bearssl=PATH],[where to look for BearSSL, PATH points to the installation root]),[ 88 | OPT_BEARSSL=$withval 89 | if test X"$withval" != Xno; then 90 | - test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }BearSSL") 91 | + test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }BearSSL" 92 | fi 93 | +]) 94 | 95 | OPT_RUSTLS=no 96 | AC_ARG_WITH(rustls,dnl 97 | -AS_HELP_STRING([--with-rustls=PATH],[where to look for rustls, PATH points to the installation root]), 98 | +AS_HELP_STRING([--with-rustls=PATH],[where to look for rustls, PATH points to the installation root]),[ 99 | OPT_RUSTLS=$withval 100 | if test X"$withval" != Xno; then 101 | - test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }rustls") 102 | + test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }rustls" 103 | fi 104 | +]) 105 | 106 | OPT_NSS_AWARE=no 107 | AC_ARG_WITH(nss-deprecated,dnl 108 | -AS_HELP_STRING([--with-nss-deprecated],[confirm you realize NSS is going away]), 109 | +AS_HELP_STRING([--with-nss-deprecated],[confirm you realize NSS is going away]),[ 110 | if test X"$withval" != Xno; then 111 | OPT_NSS_AWARE=$withval 112 | fi 113 | -) 114 | +]) 115 | 116 | OPT_NSS=no 117 | AC_ARG_WITH(nss,dnl 118 | -AS_HELP_STRING([--with-nss=PATH],[where to look for NSS, PATH points to the installation root]), 119 | +AS_HELP_STRING([--with-nss=PATH],[where to look for NSS, PATH points to the installation root]),[ 120 | OPT_NSS=$withval 121 | if test X"$withval" != Xno; then 122 | 123 | @@ -283,7 +292,7 @@ AS_HELP_STRING([--with-nss=PATH],[where to look for NSS, PATH points to the inst 124 | 125 | test -z "TLSCHOICE" || TLSCHOICE="${TLSCHOICE:+$TLSCHOICE, }NSS" 126 | fi 127 | -) 128 | +]) 129 | 130 | dnl If no TLS choice has been made, check if it was explicitly disabled or 131 | dnl error out to force the user to decide. 132 | -------------------------------------------------------------------------------- /deps/versions: -------------------------------------------------------------------------------- 1 | AWS_LAMBDA_CPP_RELEASE=0.2.6 2 | CURL_MAJOR_VERSION=7 3 | CURL_MINOR_VERSION=83 4 | CURL_PATCH_VERSION=1 5 | -------------------------------------------------------------------------------- /requirements/base.txt: -------------------------------------------------------------------------------- 1 | simplejson>=3.20.1 2 | snapshot-restore-py>=1.0.0 3 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | coverage>=4.4.0 2 | flake8>=3.3.0 3 | tox>=2.2.1 4 | pytest-cov>=2.4.0 5 | pylint>=1.7.2 6 | black>=20.8b0 7 | bandit>=1.6.2 8 | 9 | # Test requirements 10 | pytest>=3.0.7 11 | mock>=2.0.0 12 | parameterized>=0.9.0 -------------------------------------------------------------------------------- /scripts/preinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | set -e 4 | 5 | ARTIFACTS_DIR=$(pwd)/deps/artifacts 6 | 7 | if [ "$(uname)" = "Darwin" ]; then 8 | echo "aws-lambda-cpp does not build on OS X. Skipping the preinstall step." 9 | else 10 | if [ -x "$(command -v cmake3)" ]; then 11 | CMAKE=cmake3 12 | elif [ -x "$(command -v cmake)" ]; then 13 | CMAKE=cmake 14 | else 15 | echo 'Error: cmake is not installed.' >&2 16 | exit 1 17 | fi 18 | 19 | cd deps 20 | . ./versions 21 | 22 | CURL_VERSION="${CURL_MAJOR_VERSION}.${CURL_MINOR_VERSION}.${CURL_PATCH_VERSION}" 23 | 24 | rm -rf ./curl-$CURL_VERSION 25 | rm -rf ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE 26 | 27 | # unpack dependencies 28 | tar xzf ./curl-$CURL_VERSION.tar.gz --no-same-owner && \ 29 | tar xzf ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz --no-same-owner 30 | 31 | ( 32 | # Build Curl 33 | cd curl-$CURL_VERSION && \ 34 | ./buildconf && \ 35 | ./configure \ 36 | --prefix "$ARTIFACTS_DIR" \ 37 | --disable-shared \ 38 | --without-ssl \ 39 | --with-pic \ 40 | --without-zlib && \ 41 | make && \ 42 | make install 43 | ) 44 | 45 | ( 46 | # Build aws-lambda-cpp 47 | mkdir -p ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE/build && \ 48 | cd ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE/build 49 | 50 | $CMAKE .. \ 51 | -DCMAKE_CXX_FLAGS="-fPIC" \ 52 | -DCMAKE_INSTALL_PREFIX="$ARTIFACTS_DIR" \ 53 | -DENABLE_LTO=$ENABLE_LTO \ 54 | -DCMAKE_MODULE_PATH="$ARTIFACTS_DIR"/lib/pkgconfig && \ 55 | make && make install 56 | ) 57 | fi -------------------------------------------------------------------------------- /scripts/update_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | set -x 4 | 5 | cd deps 6 | source versions 7 | 8 | # Clean up old files 9 | rm -f aws-lambda-cpp-*.tar.gz && rm -f curl-*.tar.gz 10 | 11 | 12 | LIBCURL="curl-${CURL_MAJOR_VERSION}.${CURL_MINOR_VERSION}.${CURL_PATCH_VERSION}" 13 | 14 | # Grab Curl 15 | wget -c "https://github.com/curl/curl/releases/download/curl-${CURL_MAJOR_VERSION}_${CURL_MINOR_VERSION}_${CURL_PATCH_VERSION}/$LIBCURL.tar.gz" -O - | tar -xz 16 | ( 17 | cd $LIBCURL && \ 18 | patch -p1 < ../patches/libcurl-configure-template.patch 19 | ) 20 | 21 | tar -czf $LIBCURL.tar.gz $LIBCURL --no-same-owner && rm -rf $LIBCURL 22 | 23 | # Grab aws-lambda-cpp 24 | wget -c https://github.com/awslabs/aws-lambda-cpp/archive/v$AWS_LAMBDA_CPP_RELEASE.tar.gz -O - | tar -xz 25 | 26 | ## Apply patches to aws-lambda-cpp 27 | ( 28 | cd aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE && \ 29 | patch -p1 < ../patches/aws-lambda-cpp-add-xray-response.patch && \ 30 | patch -p1 < ../patches/aws-lambda-cpp-posting-init-errors.patch && \ 31 | patch -p1 < ../patches/aws-lambda-cpp-make-the-runtime-client-user-agent-overrideable.patch && \ 32 | patch -p1 < ../patches/aws-lambda-cpp-make-lto-optional.patch && \ 33 | patch -p1 < ../patches/aws-lambda-cpp-add-content-type.patch && \ 34 | patch -p1 < ../patches/aws-lambda-cpp-add-tenant-id.patch 35 | ) 36 | 37 | ## Pack again and remove the folder 38 | tar -czf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE --no-same-owner && \ 39 | rm -rf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE 40 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | import io 6 | import os 7 | import platform 8 | from subprocess import check_call, check_output 9 | from setuptools import Extension, find_packages, setup 10 | from awslambdaric import __version__ 11 | 12 | 13 | def get_curl_extra_linker_flags(): 14 | # We do not want to build the dependencies during packaging 15 | if platform.system() != "Linux" or os.getenv("BUILD") == "true": 16 | return [] 17 | 18 | # Build the dependencies 19 | check_call(["./scripts/preinstall.sh"]) 20 | 21 | # call curl-config to get the required linker flags 22 | cmd = ["./deps/artifacts/bin/curl-config", "--static-libs"] 23 | curl_config = check_output(cmd).decode("utf-8").replace("\n", "") 24 | 25 | # It is expected that the result of the curl-config call is similar to 26 | # "/tmp/pip-req-build-g9dlug7g/deps/artifacts/lib/libcurl.a -lidn2" 27 | # we want to return just the extra flags 28 | flags = curl_config.split(" ")[1:] 29 | 30 | return flags 31 | 32 | 33 | def get_runtime_client_extension(): 34 | if platform.system() != "Linux" and os.getenv("BUILD") != "true": 35 | print( 36 | "The native runtime_client only builds on Linux. Skipping its compilation." 37 | ) 38 | return [] 39 | 40 | runtime_client = Extension( 41 | "runtime_client", 42 | ["awslambdaric/runtime_client.cpp"], 43 | extra_compile_args=["--std=c++11"], 44 | library_dirs=["deps/artifacts/lib", "deps/artifacts/lib64"], 45 | libraries=["aws-lambda-runtime", "curl"], 46 | extra_link_args=get_curl_extra_linker_flags(), 47 | include_dirs=["deps/artifacts/include"], 48 | ) 49 | 50 | return [runtime_client] 51 | 52 | 53 | def read(*filenames, **kwargs): 54 | encoding = kwargs.get("encoding", "utf-8") 55 | sep = kwargs.get("sep", os.linesep) 56 | buf = [] 57 | for filename in filenames: 58 | with io.open(filename, encoding=encoding) as f: 59 | buf.append(f.read()) 60 | return sep.join(buf) 61 | 62 | 63 | def read_requirements(req="base.txt"): 64 | content = read(os.path.join("requirements", req)) 65 | return [ 66 | line for line in content.split(os.linesep) if not line.strip().startswith("#") 67 | ] 68 | 69 | 70 | setup( 71 | name="awslambdaric", 72 | version=__version__, 73 | author="Amazon Web Services", 74 | description="AWS Lambda Runtime Interface Client for Python", 75 | long_description=read("README.md"), 76 | long_description_content_type="text/markdown", 77 | url="https://github.com/aws/aws-lambda-python-runtime-interface-client", 78 | packages=find_packages( 79 | exclude=("tests", "tests.*", "docs", "examples", "versions") 80 | ), 81 | install_requires=read_requirements("base.txt"), 82 | classifiers=[ 83 | "Development Status :: 5 - Production/Stable", 84 | "Intended Audience :: Developers", 85 | "Natural Language :: English", 86 | "Programming Language :: Python :: 3", 87 | "Programming Language :: Python :: 3.9", 88 | "Programming Language :: Python :: 3.10", 89 | "Programming Language :: Python :: 3.11", 90 | "Programming Language :: Python :: 3.12", 91 | "Programming Language :: Python :: 3.13", 92 | "License :: OSI Approved :: Apache Software License", 93 | "Operating System :: OS Independent", 94 | ], 95 | python_requires=">=3.9", 96 | ext_modules=get_runtime_client_extension(), 97 | test_suite="tests", 98 | ) 99 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-lambda-python-runtime-interface-client/3f43f4d089600669435038d7e1fa41145d9ff94f/tests/__init__.py -------------------------------------------------------------------------------- /tests/integration/codebuild-local/Dockerfile.agent: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/amazonlinux/amazonlinux:2 2 | 3 | RUN amazon-linux-extras enable docker && \ 4 | yum clean metadata && \ 5 | yum install -y docker tar 6 | -------------------------------------------------------------------------------- /tests/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 $awsconfig_flag 147 | then 148 | if [ -d "$HOME/.aws" ] 149 | then 150 | configuration_file_path=$(allOSRealPath "$HOME/.aws") 151 | docker_command+=" -e \"AWS_CONFIGURATION=$configuration_file_path\"" 152 | else 153 | docker_command+=" -e \"AWS_CONFIGURATION=NONE\"" 154 | fi 155 | 156 | if [ -n "$aws_profile" ] 157 | then 158 | docker_command+=" -e \"AWS_PROFILE=$aws_profile\"" 159 | fi 160 | 161 | docker_command+="$(env | grep ^AWS_ | while read -r line; do echo " -e \"$line\""; done )" 162 | fi 163 | 164 | if $mount_src_dir_flag 165 | then 166 | docker_command+=" -e \"MOUNT_SOURCE_DIRECTORY=TRUE\"" 167 | fi 168 | 169 | if $docker_privileged_mode_flag 170 | then 171 | docker_command+=" -e \"DOCKER_PRIVILEGED_MODE=TRUE\"" 172 | fi 173 | 174 | if isOSWindows 175 | then 176 | docker_command+=" -e \"INITIATOR=$USERNAME\"" 177 | else 178 | docker_command+=" -e \"INITIATOR=$USER\"" 179 | fi 180 | 181 | if [ -n "$local_agent_image" ] 182 | then 183 | docker_command+=" $local_agent_image" 184 | else 185 | docker_command+=" public.ecr.aws/codebuild/local-builds:latest" 186 | fi 187 | 188 | # Note we do not expose the AWS_SECRET_ACCESS_KEY or the AWS_SESSION_TOKEN 189 | exposed_command=$docker_command 190 | secure_variables=( "AWS_SECRET_ACCESS_KEY=" "AWS_SESSION_TOKEN=") 191 | for variable in "${secure_variables[@]}" 192 | do 193 | exposed_command="$(echo $exposed_command | sed "s/\($variable\)[^ ]*/\1********\"/")" 194 | done 195 | 196 | echo "Build Command:" 197 | echo "" 198 | echo $exposed_command 199 | echo "" 200 | 201 | eval $docker_command -------------------------------------------------------------------------------- /tests/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 | DRYRUN="${DRYRUN-0}" 8 | DISTRO="${DISTRO:=""}" 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 | echo $DISTRO $BUILDSPEC_YML_DIR 58 | ls $BUILDSPEC_YML_DIR 59 | HAS_YML=0 60 | for f in "$BUILDSPEC_YML_DIR"/*"$DISTRO"*.yml ; do 61 | [ -f "$f" ] || continue; 62 | do_one_yaml "$f" 63 | HAS_YML=1 64 | done 65 | 66 | if (( HAS_YML == 0 )); then 67 | >&2 echo At least one buildspec is required. 68 | exit 2 69 | fi 70 | } 71 | 72 | main "$@" 73 | -------------------------------------------------------------------------------- /tests/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 | set -x 28 | BUILDSPEC_YML="$1" 29 | OS_DISTRIBUTION="$2" 30 | DISTRO_VERSION="$3" 31 | RUNTIME_VERSION="$4" 32 | EXTRA_ENV="${5-}" 33 | 34 | CODEBUILD_TEMP_DIR=$(mktemp -d codebuild."$OS_DISTRIBUTION"-"$DISTRO_VERSION"-"$RUNTIME_VERSION".XXXXXXXXXX) 35 | trap 'rm -rf $CODEBUILD_TEMP_DIR' EXIT 36 | 37 | # Create an env file for codebuild_build. 38 | ENVFILE="$CODEBUILD_TEMP_DIR/.env" 39 | if [ -f "$EXTRA_ENV" ]; then 40 | cat "$EXTRA_ENV" > "$ENVFILE" 41 | fi 42 | { 43 | echo "" 44 | echo "OS_DISTRIBUTION=$OS_DISTRIBUTION" 45 | echo "DISTRO_VERSION=$DISTRO_VERSION" 46 | echo "RUNTIME_VERSION=$RUNTIME_VERSION" 47 | } >> "$ENVFILE" 48 | 49 | ARTIFACTS_DIR="$CODEBUILD_TEMP_DIR/artifacts" 50 | mkdir -p "$ARTIFACTS_DIR" 51 | 52 | # Run CodeBuild local agent. 53 | "$(dirname "$0")"/codebuild_build.sh \ 54 | -i "$CODEBUILD_IMAGE_TAG" \ 55 | -a "$ARTIFACTS_DIR" \ 56 | -e "$ENVFILE" \ 57 | -b "$BUILDSPEC_YML" 58 | } 59 | 60 | main "$@" 61 | -------------------------------------------------------------------------------- /tests/integration/codebuild/buildspec.os.alpine.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: alpine 6 | PYTHON_LOCATION: "/usr/local/bin/python" 7 | TEST_NAME: "aws-lambda-python-rtc-alpine-test" 8 | batch: 9 | build-matrix: 10 | static: 11 | ignore-failure: false 12 | env: 13 | privileged-mode: true 14 | dynamic: 15 | env: 16 | variables: 17 | DISTRO_VERSION: 18 | - "3.19" 19 | - "3.20" 20 | RUNTIME_VERSION: 21 | - "3.9" 22 | - "3.10" 23 | - "3.11" 24 | - "3.12" 25 | - "3.13" 26 | phases: 27 | pre_build: 28 | commands: 29 | - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 30 | - echo "Extracting and including the Runtime Interface Emulator" 31 | - SCRATCH_DIR=".scratch" 32 | - mkdir "${SCRATCH_DIR}" 33 | - ARCHITECTURE=$(arch) 34 | - > 35 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 36 | RIE="aws-lambda-rie" 37 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 38 | RIE="aws-lambda-rie-arm64" 39 | else 40 | echo "Architecture $ARCHITECTURE is not currently supported." 41 | exit 1 42 | fi 43 | - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 44 | - > 45 | cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 46 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 47 | - > 48 | echo "RUN apk add curl" >> \ 49 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 50 | - > 51 | echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ 52 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 53 | - > 54 | echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json 55 | service docker restart 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 | --load 64 | build: 65 | commands: 66 | - set -x 67 | - echo "Running Image ${IMAGE_TAG}" 68 | - docker network create "${TEST_NAME}-network" 69 | - > 70 | docker run \ 71 | --detach \ 72 | --name "${TEST_NAME}-app" \ 73 | --network "${TEST_NAME}-network" \ 74 | --entrypoint="" \ 75 | "${IMAGE_TAG}" \ 76 | sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler" 77 | - sleep 2 78 | - > 79 | docker run \ 80 | --name "${TEST_NAME}-tester" \ 81 | --env "TARGET=${TEST_NAME}-app" \ 82 | --network "${TEST_NAME}-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 "${TEST_NAME}-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: ${TEST_NAME}-app----------" 97 | echo 98 | docker logs "${TEST_NAME}-app" || true 99 | echo 100 | echo "---------------------------------------------------" 101 | echo "--------Container Logs: ${TEST_NAME}-tester--------" 102 | echo 103 | docker logs "${TEST_NAME}-tester" || true 104 | echo 105 | echo "---------------------------------------------------" 106 | - echo "Cleaning up..." 107 | - docker stop "${TEST_NAME}-app" || true 108 | - docker rm --force "${TEST_NAME}-app" || true 109 | - docker stop "${TEST_NAME}-tester" || true 110 | - docker rm --force "${TEST_NAME}-tester" || true 111 | - docker network rm "${TEST_NAME}-network" || true 112 | -------------------------------------------------------------------------------- /tests/integration/codebuild/buildspec.os.amazonlinux.2.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: amazonlinux2 6 | PYTHON_LOCATION: "/usr/local/bin/python3" 7 | TEST_NAME: "aws-lambda-python-rtc-amazonlinux-test" 8 | batch: 9 | build-matrix: 10 | static: 11 | ignore-failure: false 12 | env: 13 | privileged-mode: true 14 | dynamic: 15 | env: 16 | variables: 17 | DISTRO_VERSION: 18 | - "2" 19 | RUNTIME_VERSION: 20 | - "3.9" 21 | - "3.10" 22 | - "3.11" 23 | phases: 24 | pre_build: 25 | commands: 26 | - export IMAGE_TAG="python-${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 tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 41 | - > 42 | cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 43 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 44 | - > 45 | echo "COPY ${SCRATCH_DIR}/${RIE} /usr/bin/${RIE}" >> \ 46 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 47 | - > 48 | echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json 49 | service docker restart 50 | - echo "Building image ${IMAGE_TAG}" 51 | - > 52 | docker build . \ 53 | -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ 54 | -t "${IMAGE_TAG}" \ 55 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 56 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ 57 | --build-arg ARCHITECTURE="${ARCHITECTURE}" \ 58 | --load 59 | build: 60 | commands: 61 | - set -x 62 | - echo "Running Image ${IMAGE_TAG}" 63 | - docker network create "${TEST_NAME}-network" 64 | - > 65 | docker run \ 66 | --detach \ 67 | --name "${TEST_NAME}-app" \ 68 | --network "${TEST_NAME}-network" \ 69 | --entrypoint="" \ 70 | "${IMAGE_TAG}" \ 71 | sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler" 72 | - sleep 2 73 | - > 74 | docker run \ 75 | --name "${TEST_NAME}-tester" \ 76 | --env "TARGET=${TEST_NAME}-app" \ 77 | --network "${TEST_NAME}-network" \ 78 | --entrypoint="" \ 79 | "${IMAGE_TAG}" \ 80 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 81 | - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)" 82 | - expected='success' 83 | - | 84 | echo "Response: ${actual}" 85 | if [[ "$actual" != "$expected" ]]; then 86 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 87 | exit -1 88 | fi 89 | finally: 90 | - | 91 | echo "---------Container Logs: ${TEST_NAME}-app----------" 92 | echo 93 | docker logs "${TEST_NAME}-app" || true 94 | echo 95 | echo "---------------------------------------------------" 96 | echo "--------Container Logs: ${TEST_NAME}-tester--------" 97 | echo 98 | docker logs "${TEST_NAME}-tester" || true 99 | echo 100 | echo "---------------------------------------------------" 101 | - echo "Cleaning up..." 102 | - docker stop "${TEST_NAME}-app" || true 103 | - docker rm --force "${TEST_NAME}-app" || true 104 | - docker stop "${TEST_NAME}-tester" || true 105 | - docker rm --force "${TEST_NAME}-tester" || true 106 | - docker network rm "${TEST_NAME}-network" || true 107 | -------------------------------------------------------------------------------- /tests/integration/codebuild/buildspec.os.amazonlinux.2023.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: amazonlinux2023 6 | PYTHON_LOCATION: "/usr/local/bin/python3" 7 | TEST_NAME: "aws-lambda-python-rtc-amazonlinux-test" 8 | batch: 9 | build-matrix: 10 | static: 11 | ignore-failure: false 12 | env: 13 | privileged-mode: true 14 | dynamic: 15 | env: 16 | variables: 17 | DISTRO_VERSION: 18 | - "2023" 19 | RUNTIME_VERSION: 20 | - "3.12" 21 | - "3.13" 22 | phases: 23 | pre_build: 24 | commands: 25 | - export IMAGE_TAG="python-${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 tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 40 | - > 41 | cp "tests/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 | echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json 48 | service docker restart 49 | - echo "Building image ${IMAGE_TAG}" 50 | - > 51 | docker build . \ 52 | -f "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" \ 53 | -t "${IMAGE_TAG}" \ 54 | --build-arg RUNTIME_VERSION="${RUNTIME_VERSION}" \ 55 | --build-arg DISTRO_VERSION="${DISTRO_VERSION}" \ 56 | --build-arg ARCHITECTURE="${ARCHITECTURE}" \ 57 | --load 58 | build: 59 | commands: 60 | - set -x 61 | - echo "Running Image ${IMAGE_TAG}" 62 | - docker network create "${TEST_NAME}-network" 63 | - > 64 | docker run \ 65 | --detach \ 66 | --name "${TEST_NAME}-app" \ 67 | --network "${TEST_NAME}-network" \ 68 | --entrypoint="" \ 69 | "${IMAGE_TAG}" \ 70 | sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler" 71 | - sleep 2 72 | - > 73 | docker run \ 74 | --name "${TEST_NAME}-tester" \ 75 | --env "TARGET=${TEST_NAME}-app" \ 76 | --network "${TEST_NAME}-network" \ 77 | --entrypoint="" \ 78 | "${IMAGE_TAG}" \ 79 | sh -c 'curl -X POST "http://${TARGET}:8080/2015-03-31/functions/function/invocations" -d "{}" --max-time 10' 80 | - actual="$(docker logs --tail 1 "${TEST_NAME}-tester" | xargs)" 81 | - expected='success' 82 | - | 83 | echo "Response: ${actual}" 84 | if [[ "$actual" != "$expected" ]]; then 85 | echo "fail! runtime: $RUNTIME - expected output $expected - got $actual" 86 | exit -1 87 | fi 88 | finally: 89 | - | 90 | echo "---------Container Logs: ${TEST_NAME}-app----------" 91 | echo 92 | docker logs "${TEST_NAME}-app" || true 93 | echo 94 | echo "---------------------------------------------------" 95 | echo "--------Container Logs: ${TEST_NAME}-tester--------" 96 | echo 97 | docker logs "${TEST_NAME}-tester" || true 98 | echo 99 | echo "---------------------------------------------------" 100 | - echo "Cleaning up..." 101 | - docker stop "${TEST_NAME}-app" || true 102 | - docker rm --force "${TEST_NAME}-app" || true 103 | - docker stop "${TEST_NAME}-tester" || true 104 | - docker rm --force "${TEST_NAME}-tester" || true 105 | - docker network rm "${TEST_NAME}-network" || true 106 | -------------------------------------------------------------------------------- /tests/integration/codebuild/buildspec.os.debian.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: debian 6 | PYTHON_LOCATION: "/usr/local/bin/python" 7 | TEST_NAME: "aws-lambda-python-rtc-debian-test" 8 | batch: 9 | build-matrix: 10 | static: 11 | ignore-failure: false 12 | env: 13 | privileged-mode: true 14 | dynamic: 15 | env: 16 | variables: 17 | DISTRO_VERSION: 18 | - "bookworm" 19 | - "bullseye" 20 | RUNTIME_VERSION: 21 | - "3.9" 22 | - "3.10" 23 | - "3.11" 24 | - "3.12" 25 | - "3.13" 26 | phases: 27 | pre_build: 28 | commands: 29 | - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 30 | - echo "Extracting and including the Runtime Interface Emulator" 31 | - SCRATCH_DIR=".scratch" 32 | - mkdir "${SCRATCH_DIR}" 33 | - ARCHITECTURE=$(arch) 34 | - > 35 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 36 | RIE="aws-lambda-rie" 37 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 38 | RIE="aws-lambda-rie-arm64" 39 | else 40 | echo "Architecture $ARCHITECTURE is not currently supported." 41 | exit 1 42 | fi 43 | - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 44 | - > 45 | cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 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 | echo "RUN apt-get update && apt-get install -y curl" >> \ 52 | "${SCRATCH_DIR}/Dockerfile.echo.${OS_DISTRIBUTION}.tmp" 53 | - > 54 | echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json 55 | service docker restart 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 | --load 64 | build: 65 | commands: 66 | - set -x 67 | - echo "Running Image ${IMAGE_TAG}" 68 | - docker network create "${TEST_NAME}-network" 69 | - > 70 | docker run \ 71 | --detach \ 72 | --name "${TEST_NAME}-app" \ 73 | --network "${TEST_NAME}-network" \ 74 | --entrypoint="" \ 75 | "${IMAGE_TAG}" \ 76 | sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler" 77 | - sleep 2 78 | - > 79 | docker run \ 80 | --name "${TEST_NAME}-tester" \ 81 | --env "TARGET=${TEST_NAME}-app" \ 82 | --network "${TEST_NAME}-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 "${TEST_NAME}-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: ${TEST_NAME}-app----------" 97 | echo 98 | docker logs "${TEST_NAME}-app" || true 99 | echo 100 | echo "---------------------------------------------------" 101 | echo "--------Container Logs: ${TEST_NAME}-tester--------" 102 | echo 103 | docker logs "${TEST_NAME}-tester" || true 104 | echo 105 | echo "---------------------------------------------------" 106 | - echo "Cleaning up..." 107 | - docker stop "${TEST_NAME}-app" || true 108 | - docker rm --force "${TEST_NAME}-app" || true 109 | - docker stop "${TEST_NAME}-tester" || true 110 | - docker rm --force "${TEST_NAME}-tester" || true 111 | - docker network rm "${TEST_NAME}-network" || true 112 | -------------------------------------------------------------------------------- /tests/integration/codebuild/buildspec.os.ubuntu.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | OS_DISTRIBUTION: ubuntu 6 | PYTHON_LOCATION: "/usr/bin/python" 7 | TEST_NAME: "aws-lambda-python-rtc-ubuntu-test" 8 | batch: 9 | build-matrix: 10 | static: 11 | ignore-failure: false 12 | env: 13 | privileged-mode: true 14 | dynamic: 15 | env: 16 | variables: 17 | DISTRO_VERSION: 18 | - "22.04" 19 | - "24.04" 20 | RUNTIME_VERSION: 21 | - "3.9" 22 | - "3.10" 23 | - "3.11" 24 | - "3.12" 25 | - "3.13" 26 | phases: 27 | pre_build: 28 | commands: 29 | - export IMAGE_TAG="python-${OS_DISTRIBUTION}-${DISTRO_VERSION}:${RUNTIME_VERSION}" 30 | - echo "Extracting and including the Runtime Interface Emulator" 31 | - SCRATCH_DIR=".scratch" 32 | - mkdir "${SCRATCH_DIR}" 33 | - ARCHITECTURE=$(arch) 34 | - > 35 | if [[ "$ARCHITECTURE" == "x86_64" ]]; then 36 | RIE="aws-lambda-rie" 37 | elif [[ "$ARCHITECTURE" == "aarch64" ]]; then 38 | RIE="aws-lambda-rie-arm64" 39 | else 40 | echo "Architecture $ARCHITECTURE is not currently supported." 41 | exit 1 42 | fi 43 | - tar -xvf tests/integration/resources/${RIE}.tar.gz --directory "${SCRATCH_DIR}" 44 | - > 45 | cp "tests/integration/docker/Dockerfile.echo.${OS_DISTRIBUTION}" \ 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 | echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' > /etc/docker/daemon.json 52 | service docker restart 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 | --load 61 | build: 62 | commands: 63 | - set -x 64 | - echo "Running Image ${IMAGE_TAG}" 65 | - docker network create "${TEST_NAME}-network" 66 | - PYTHON_LOCATION=${PYTHON_LOCATION}${RUNTIME_VERSION} 67 | - > 68 | docker run \ 69 | --detach \ 70 | --name "${TEST_NAME}-app" \ 71 | --network "${TEST_NAME}-network" \ 72 | --entrypoint="" \ 73 | "${IMAGE_TAG}" \ 74 | sh -c "/usr/bin/${RIE} ${PYTHON_LOCATION} -m awslambdaric app.handler" 75 | - sleep 2 76 | - > 77 | docker run \ 78 | --name "${TEST_NAME}-tester" \ 79 | --env "TARGET=${TEST_NAME}-app" \ 80 | --network "${TEST_NAME}-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 "${TEST_NAME}-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: ${TEST_NAME}-app----------" 95 | echo 96 | docker logs "${TEST_NAME}-app" || true 97 | echo 98 | echo "---------------------------------------------------" 99 | echo "--------Container Logs: ${TEST_NAME}-tester--------" 100 | echo 101 | docker logs "${TEST_NAME}-tester" || true 102 | echo 103 | echo "---------------------------------------------------" 104 | - echo "Cleaning up..." 105 | - docker stop "${TEST_NAME}-app" || true 106 | - docker rm --force "${TEST_NAME}-app" || true 107 | - docker stop "${TEST_NAME}-tester" || true 108 | - docker rm --force "${TEST_NAME}-tester" || true 109 | - docker network rm "${TEST_NAME}-network" || true 110 | -------------------------------------------------------------------------------- /tests/integration/docker-compose.template.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | function: 4 | build: 5 | context: . 6 | args: 7 | RUNTIME_VERSION: "${runtime_version}" 8 | DISTRO_VERSION: "${distro_version}" 9 | dockerfile: ./docker/Dockerfile.echo.${DISTRO} 10 | environment: 11 | - AWS_LAMBDA_RUNTIME_API=runtime:9001 12 | 13 | runtime: 14 | build: 15 | context: . 16 | dockerfile: ../docker-helpers/Dockerfile.runtime 17 | 18 | invoker: 19 | build: 20 | context: . 21 | dockerfile: ../docker-helpers/Dockerfile.aws-cli 22 | entrypoint: [ 23 | aws, lambda, invoke, 24 | --endpoint, http://runtime:9001, 25 | --no-sign-request, 26 | --region, us-west-2, 27 | --function-name, ignored, 28 | --payload, '{ "name": "Lambda" }', 29 | /dev/stdout 30 | ] 31 | -------------------------------------------------------------------------------- /tests/integration/docker/Dockerfile.echo.alpine: -------------------------------------------------------------------------------- 1 | # Define global args 2 | ARG RUNTIME_VERSION 3 | ARG DISTRO_VERSION 4 | 5 | # Stage 1 - bundle base image + runtime interface client 6 | # Grab a fresh copy of the image and install GCC 7 | FROM public.ecr.aws/docker/library/python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} AS python-alpine 8 | # Install libstdc++ 9 | RUN apk add --no-cache \ 10 | libstdc++ \ 11 | binutils 12 | 13 | 14 | # Stage 2 - build function and dependencies 15 | FROM python-alpine AS build-image 16 | # Install aws-lambda-cpp build dependencies 17 | RUN apk add --no-cache \ 18 | build-base \ 19 | libtool \ 20 | autoconf \ 21 | automake \ 22 | elfutils-dev \ 23 | make \ 24 | cmake \ 25 | libcurl 26 | 27 | # Include global args in this stage of the build 28 | ARG RIC_BUILD_DIR="/home/build/" 29 | # Create function directory 30 | RUN mkdir -p ${RIC_BUILD_DIR} 31 | # Copy function code and Runtime Interface Client .tgz 32 | WORKDIR ${RIC_BUILD_DIR} 33 | COPY . . 34 | RUN pip3 install setuptools 35 | RUN make init build test && \ 36 | mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz 37 | 38 | # Include global args in this stage of the build 39 | ARG FUNCTION_DIR="/home/app/" 40 | # Create function directory 41 | RUN mkdir -p ${FUNCTION_DIR} 42 | # Copy function code 43 | COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR} 44 | 45 | # Install the function's dependencies 46 | WORKDIR ${FUNCTION_DIR} 47 | RUN python${RUNTIME_VERSION} -m pip install \ 48 | ${RIC_BUILD_DIR}/dist/awslambdaric-test.tar.gz \ 49 | --target ${FUNCTION_DIR} 50 | 51 | 52 | # Stage 3 - final runtime interface client image 53 | # Grab a fresh copy of the Python image 54 | FROM python-alpine 55 | 56 | # Include global arg in this stage of the build 57 | ARG FUNCTION_DIR="/home/app/" 58 | # Set working directory to function root directory 59 | WORKDIR ${FUNCTION_DIR} 60 | # Copy in the built dependencies 61 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 62 | 63 | ENTRYPOINT [ "/usr/local/bin/python", "-m", "awslambdaric" ] 64 | CMD [ "app.handler" ] 65 | -------------------------------------------------------------------------------- /tests/integration/docker/Dockerfile.echo.amazonlinux2: -------------------------------------------------------------------------------- 1 | ARG DISTRO_VERSION 2 | # Stage 1 - bundle base image + runtime interface client 3 | # Grab a fresh copy of the image and install Python 4 | FROM public.ecr.aws/amazonlinux/amazonlinux:${DISTRO_VERSION} AS python-amazonlinux-builder 5 | 6 | ARG RUNTIME_VERSION 7 | 8 | # Install apt dependencies 9 | RUN yum install -y \ 10 | gcc \ 11 | gcc-c++ \ 12 | tar \ 13 | gzip \ 14 | make \ 15 | autoconf \ 16 | automake \ 17 | freetype-devel \ 18 | yum-utils \ 19 | findutils \ 20 | wget \ 21 | openssl11 \ 22 | openssl11-devel \ 23 | bzip2-devel \ 24 | libffi-devel \ 25 | sqlite-devel 26 | 27 | RUN RUNTIME_LATEST_VERSION=${RUNTIME_VERSION}.$(curl -s https://www.python.org/ftp/python/ | \ 28 | grep -oE "href=\"$(echo ${RUNTIME_VERSION} | sed "s/\\./\\\./g")\.[0-9]+" | \ 29 | cut -d. -f3 | \ 30 | sort -rn | \ 31 | while read -r patch; do \ 32 | $(wget -c https://www.python.org/ftp/python/${RUNTIME_VERSION}.$patch/Python-${RUNTIME_VERSION}.$patch.tgz -O Python-${RUNTIME_VERSION}.$patch.tgz); \ 33 | [ $? -eq 0 ] && echo $patch && break; \ 34 | done) \ 35 | && tar -xzf Python-${RUNTIME_LATEST_VERSION}.tgz \ 36 | && cd Python-${RUNTIME_LATEST_VERSION} \ 37 | && ./configure --prefix=/usr/local --enable-shared \ 38 | && make \ 39 | && make install \ 40 | && ln -s /usr/local/bin/python${RUNTIME_VERSION} /usr/local/bin/python${RUNTIME_LATEST_VERSION} 41 | 42 | # Stage 2 - clean python build dependencies 43 | FROM public.ecr.aws/amazonlinux/amazonlinux:${DISTRO_VERSION} AS python-amazonlinux 44 | RUN yum install -y \ 45 | libffi-devel 46 | 47 | # Copy the compiled python to /usr/local 48 | COPY --from=python-amazonlinux-builder /usr/local /usr/local 49 | ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH 50 | 51 | # Stage 3 - build function and dependencies 52 | FROM python-amazonlinux-builder AS build-image 53 | ARG RUNTIME_VERSION 54 | ARG ARCHITECTURE 55 | 56 | # Install aws-lambda-cpp build dependencies 57 | RUN yum install -y \ 58 | tar \ 59 | gzip \ 60 | make \ 61 | autoconf \ 62 | automake \ 63 | libtool \ 64 | libcurl-devel \ 65 | gcc-c++ \ 66 | wget 67 | 68 | # Install a modern CMake 69 | RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0-linux-${ARCHITECTURE}.sh && \ 70 | sh cmake-install --skip-license --prefix=/usr --exclude-subdirectory; 71 | 72 | ENV PATH=/usr/local/bin:$PATH 73 | ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH 74 | 75 | 76 | # Include global args in this stage of the build 77 | ARG RIC_BUILD_DIR="/home/build/" 78 | # Create function directory 79 | RUN mkdir -p ${RIC_BUILD_DIR} 80 | # Copy function code and Runtime Interface Client .tgz 81 | WORKDIR ${RIC_BUILD_DIR} 82 | COPY . . 83 | 84 | RUN make init build test && \ 85 | mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz 86 | 87 | # Include global args in this stage of the build 88 | ARG FUNCTION_DIR="/home/app/" 89 | # Create function directory 90 | RUN mkdir -p ${FUNCTION_DIR} 91 | # Copy function code 92 | COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR} 93 | # Copy Runtime Interface Client .tgz 94 | RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz 95 | 96 | # Install the function's dependencies 97 | WORKDIR ${FUNCTION_DIR} 98 | RUN python${RUNTIME_VERSION} -m pip install \ 99 | awslambdaric-test.tar.gz \ 100 | --target ${FUNCTION_DIR} && \ 101 | rm awslambdaric-test.tar.gz 102 | 103 | 104 | # Stage 4 - final runtime interface client image 105 | # Grab a fresh copy of the Python image 106 | FROM python-amazonlinux 107 | 108 | # Include global arg in this stage of the build 109 | ARG FUNCTION_DIR="/home/app/" 110 | # Set working directory to function root directory 111 | WORKDIR ${FUNCTION_DIR} 112 | # Copy in the built dependencies 113 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 114 | 115 | ENTRYPOINT [ "/usr/local/bin/python3", "-m", "awslambdaric" ] 116 | CMD [ "app.handler" ] 117 | -------------------------------------------------------------------------------- /tests/integration/docker/Dockerfile.echo.amazonlinux2023: -------------------------------------------------------------------------------- 1 | ARG DISTRO_VERSION 2 | # Stage 1 - bundle base image + runtime interface client 3 | # Grab a fresh copy of the image and install Python 4 | FROM public.ecr.aws/amazonlinux/amazonlinux:${DISTRO_VERSION} AS python-amazonlinux-builder 5 | 6 | ARG RUNTIME_VERSION 7 | 8 | # Install apt dependencies 9 | RUN dnf install -y \ 10 | gcc \ 11 | gcc-c++ \ 12 | tar \ 13 | gzip \ 14 | make \ 15 | autoconf \ 16 | automake \ 17 | freetype-devel \ 18 | yum-utils \ 19 | findutils \ 20 | wget \ 21 | openssl \ 22 | openssl-devel \ 23 | bzip2-devel \ 24 | libffi-devel \ 25 | sqlite-devel 26 | 27 | RUN RUNTIME_LATEST_VERSION=${RUNTIME_VERSION}.$(curl -s https://www.python.org/ftp/python/ | \ 28 | grep -oE "href=\"$(echo ${RUNTIME_VERSION} | sed "s/\\./\\\./g")\.[0-9]+" | \ 29 | cut -d. -f3 | \ 30 | sort -rn | \ 31 | while read -r patch; do \ 32 | $(wget -c https://www.python.org/ftp/python/${RUNTIME_VERSION}.$patch/Python-${RUNTIME_VERSION}.$patch.tgz -O Python-${RUNTIME_VERSION}.$patch.tgz); \ 33 | [ $? -eq 0 ] && echo $patch && break; \ 34 | done) \ 35 | && tar -xzf Python-${RUNTIME_LATEST_VERSION}.tgz \ 36 | && cd Python-${RUNTIME_LATEST_VERSION} \ 37 | && ./configure --prefix=/usr/local --enable-shared \ 38 | && make \ 39 | && make install \ 40 | && ln -s /usr/local/bin/python${RUNTIME_VERSION} /usr/local/bin/python${RUNTIME_LATEST_VERSION} 41 | 42 | # Stage 2 - clean python build dependencies 43 | FROM public.ecr.aws/amazonlinux/amazonlinux:${DISTRO_VERSION} AS python-amazonlinux 44 | RUN dnf install -y \ 45 | libffi-devel 46 | 47 | # Copy the compiled python to /usr/local 48 | COPY --from=python-amazonlinux-builder /usr/local /usr/local 49 | ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH 50 | 51 | # Stage 3 - build function and dependencies 52 | FROM python-amazonlinux-builder AS build-image 53 | ARG RUNTIME_VERSION 54 | ARG ARCHITECTURE 55 | 56 | # Install aws-lambda-cpp build dependencies 57 | RUN dnf install -y \ 58 | tar \ 59 | gzip \ 60 | make \ 61 | autoconf \ 62 | automake \ 63 | libtool \ 64 | libcurl-devel \ 65 | gcc-c++ \ 66 | wget \ 67 | sqlite-devel 68 | 69 | # Install a modern CMake 70 | RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.20.0/cmake-3.20.0-linux-${ARCHITECTURE}.sh && \ 71 | sh cmake-install --skip-license --prefix=/usr --exclude-subdirectory; 72 | 73 | ENV PATH=/usr/local/bin:$PATH 74 | ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH 75 | 76 | 77 | # Include global args in this stage of the build 78 | ARG RIC_BUILD_DIR="/home/build/" 79 | # Create function directory 80 | RUN mkdir -p ${RIC_BUILD_DIR} 81 | # Copy function code and Runtime Interface Client .tgz 82 | WORKDIR ${RIC_BUILD_DIR} 83 | COPY . . 84 | 85 | # distutils no longer available in python3.12 and later 86 | # https://docs.python.org/3/whatsnew/3.12.html 87 | # https://peps.python.org/pep-0632/ 88 | RUN pip3 install setuptools 89 | RUN make init build 90 | 91 | RUN mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz 92 | RUN python${RUNTIME_VERSION} -m pip install \ 93 | ./dist/awslambdaric-test.tar.gz \ 94 | --target ${RIC_BUILD_DIR} 95 | 96 | RUN make test 97 | 98 | # Include global args in this stage of the build 99 | ARG FUNCTION_DIR="/home/app/" 100 | # Create function directory 101 | RUN mkdir -p ${FUNCTION_DIR} 102 | # Copy function code 103 | COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR} 104 | # Copy Runtime Interface Client .tgz 105 | RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz 106 | 107 | # Install the function's dependencies 108 | WORKDIR ${FUNCTION_DIR} 109 | RUN python${RUNTIME_VERSION} -m pip install \ 110 | awslambdaric-test.tar.gz \ 111 | --target ${FUNCTION_DIR} && \ 112 | rm awslambdaric-test.tar.gz 113 | 114 | 115 | # Stage 4 - final runtime interface client image 116 | # Grab a fresh copy of the Python image 117 | FROM python-amazonlinux 118 | RUN dnf install -y brotli 119 | # Include global arg in this stage of the build 120 | ARG FUNCTION_DIR="/home/app/" 121 | # Set working directory to function root directory 122 | WORKDIR ${FUNCTION_DIR} 123 | # Copy in the built dependencies 124 | COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR} 125 | 126 | ENTRYPOINT [ "/usr/local/bin/python3", "-m", "awslambdaric" ] 127 | CMD [ "app.handler" ] 128 | -------------------------------------------------------------------------------- /tests/integration/docker/Dockerfile.echo.debian: -------------------------------------------------------------------------------- 1 | ARG RUNTIME_VERSION 2 | ARG DISTRO_VERSION 3 | 4 | # Stage 1 - build function and dependencies 5 | FROM public.ecr.aws/docker/library/python:${RUNTIME_VERSION}-${DISTRO_VERSION} AS python-debian-builder 6 | 7 | # Install aws-lambda-cpp build dependencies 8 | RUN apt-get update && \ 9 | apt-get install -y \ 10 | g++ \ 11 | make \ 12 | cmake \ 13 | libcurl4-openssl-dev 14 | 15 | # Include global args in this stage of the build 16 | ARG RIC_BUILD_DIR="/home/build/" 17 | # Create function directory 18 | RUN mkdir -p ${RIC_BUILD_DIR} 19 | # Copy function code and Runtime Interface Client .tgz 20 | WORKDIR ${RIC_BUILD_DIR} 21 | COPY . . 22 | RUN pip3 install setuptools 23 | RUN make init build test && \ 24 | mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz 25 | 26 | # Include global args in this stage of the build 27 | ARG FUNCTION_DIR="/home/app/" 28 | # Create function directory 29 | RUN mkdir -p ${FUNCTION_DIR} 30 | # Copy function code 31 | COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR} 32 | # Copy Runtime Interface Client .tgz 33 | RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz 34 | 35 | # Install the function's dependencies 36 | WORKDIR ${FUNCTION_DIR} 37 | RUN pip install \ 38 | awslambdaric-test.tar.gz \ 39 | --target ${FUNCTION_DIR} && \ 40 | rm awslambdaric-test.tar.gz 41 | 42 | 43 | # Stage 2 - final runtime interface client image 44 | # Grab a fresh slim copy of the Python image 45 | FROM public.ecr.aws/docker/library/python:${RUNTIME_VERSION}-slim-${DISTRO_VERSION} 46 | 47 | # Include global arg in this stage of the build 48 | ARG FUNCTION_DIR="/home/app/" 49 | 50 | # copy the lambda function code 51 | COPY --from=python-debian-builder ${FUNCTION_DIR} ${FUNCTION_DIR} 52 | 53 | # Set working directory to function root directory 54 | WORKDIR ${FUNCTION_DIR} 55 | 56 | ENTRYPOINT ["/usr/local/bin/python", "-m", "awslambdaric"] 57 | CMD ["app.handler"] 58 | -------------------------------------------------------------------------------- /tests/integration/docker/Dockerfile.echo.ubuntu: -------------------------------------------------------------------------------- 1 | ARG DISTRO_VERSION 2 | 3 | # Stage 1 - bundle base image + runtime interface client 4 | # Grab a fresh copy of the image and install Python 5 | FROM public.ecr.aws/ubuntu/ubuntu:${DISTRO_VERSION} AS python-image 6 | 7 | ENV DEBIAN_FRONTEND=noninteractive 8 | 9 | ARG RUNTIME_VERSION 10 | 11 | # Install python and pip 12 | RUN apt-get update && apt-get install -y software-properties-common 13 | RUN add-apt-repository ppa:deadsnakes/ppa 14 | RUN apt-get update && \ 15 | apt-get install -y \ 16 | curl \ 17 | python${RUNTIME_VERSION} \ 18 | python3-pip \ 19 | python3-virtualenv 20 | 21 | # python3xx-distutils is needed for python < 3.12 22 | RUN if [ $(echo ${RUNTIME_VERSION} | cut -d '.' -f 2) -lt 12 ]; then \ 23 | apt-get install -y python${RUNTIME_VERSION}-distutils; \ 24 | fi 25 | RUN virtualenv --python /usr/bin/python${RUNTIME_VERSION} --no-setuptools /home/venv 26 | 27 | 28 | 29 | # Stage 2 - build function and dependencies 30 | FROM python-image AS python-ubuntu-builder 31 | 32 | ARG RUNTIME_VERSION 33 | 34 | # Install aws-lambda-cpp build dependencies 35 | RUN apt-get install -y \ 36 | g++ \ 37 | gcc \ 38 | tar \ 39 | gzip \ 40 | make \ 41 | cmake \ 42 | autoconf \ 43 | automake \ 44 | libtool \ 45 | libcurl4-openssl-dev \ 46 | python${RUNTIME_VERSION}-dev 47 | 48 | # Include global args in this stage of the build 49 | ARG RIC_BUILD_DIR="/home/build/" 50 | # Create function directory 51 | RUN mkdir -p ${RIC_BUILD_DIR} 52 | # Copy function code and Runtime Interface Client .tgz 53 | WORKDIR ${RIC_BUILD_DIR} 54 | COPY . . 55 | RUN . /home/venv/bin/activate && \ 56 | pip install setuptools && \ 57 | make init build test && \ 58 | mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz 59 | 60 | 61 | 62 | # Include global args in this stage of the build 63 | ARG FUNCTION_DIR="/home/app/" 64 | # Create function directory 65 | RUN mkdir -p ${FUNCTION_DIR} 66 | # Copy function code 67 | COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR} 68 | # Install the function's dependencies 69 | WORKDIR ${FUNCTION_DIR} 70 | RUN . /home/venv/bin/activate && \ 71 | pip install ${RIC_BUILD_DIR}/dist/awslambdaric-test.tar.gz --target ${FUNCTION_DIR} 72 | 73 | 74 | 75 | 76 | # Stage 3 - final runtime interface client image 77 | # Grab a fresh copy of the Python image 78 | FROM python-image 79 | 80 | # Define custom function directory 81 | ARG FUNCTION_DIR="/home/app/" 82 | 83 | # copy the lambda function code 84 | COPY --from=python-ubuntu-builder ${FUNCTION_DIR} ${FUNCTION_DIR} 85 | 86 | # Set working directory to function root directory 87 | WORKDIR ${FUNCTION_DIR} 88 | 89 | ENTRYPOINT ["/usr/bin/python${RUNTIME_VERSION}", "-m", "awslambdaric"] 90 | CMD ["app.handler"] 91 | -------------------------------------------------------------------------------- /tests/integration/resources/aws-lambda-rie-arm64.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-lambda-python-runtime-interface-client/3f43f4d089600669435038d7e1fa41145d9ff94f/tests/integration/resources/aws-lambda-rie-arm64.tar.gz -------------------------------------------------------------------------------- /tests/integration/resources/aws-lambda-rie.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-lambda-python-runtime-interface-client/3f43f4d089600669435038d7e1fa41145d9ff94f/tests/integration/resources/aws-lambda-rie.tar.gz -------------------------------------------------------------------------------- /tests/integration/test-handlers/echo/app.py: -------------------------------------------------------------------------------- 1 | def handler(event, context): 2 | return "success" 3 | -------------------------------------------------------------------------------- /tests/test_built_in_module_name/sys.py: -------------------------------------------------------------------------------- 1 | def my_handler(): 2 | return "Same name as Built in module, but different path" 3 | -------------------------------------------------------------------------------- /tests/test_handler_with_slash/test_handler.py: -------------------------------------------------------------------------------- 1 | def my_handler(): 2 | return "Good with slash" 3 | -------------------------------------------------------------------------------- /tests/test_lambda_context.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | import os 6 | import unittest 7 | from unittest.mock import MagicMock, patch 8 | 9 | from awslambdaric.lambda_context import LambdaContext 10 | 11 | 12 | class TestLambdaContext(unittest.TestCase): 13 | def setUp(self): 14 | self.org_os_environ = os.environ 15 | 16 | def tearDown(self): 17 | os.environ = self.org_os_environ 18 | 19 | def test_init(self): 20 | os.environ = { 21 | "AWS_LAMBDA_LOG_GROUP_NAME": "log-group-name1", 22 | "AWS_LAMBDA_LOG_STREAM_NAME": "log-stream-name1", 23 | "AWS_LAMBDA_FUNCTION_NAME": "function-name1", 24 | "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1234", 25 | "AWS_LAMBDA_FUNCTION_VERSION": "version1", 26 | } 27 | client_context = {"client": {}} 28 | cognito_identity = {} 29 | 30 | context = LambdaContext( 31 | "invoke-id1", client_context, cognito_identity, 1415836801000, "arn:test1" 32 | ) 33 | self.assertEqual(context.aws_request_id, "invoke-id1") 34 | self.assertEqual(context.log_group_name, "log-group-name1") 35 | self.assertEqual(context.log_stream_name, "log-stream-name1") 36 | self.assertEqual(context.function_name, "function-name1") 37 | self.assertEqual(context.memory_limit_in_mb, "1234") 38 | self.assertEqual(context.function_version, "version1") 39 | self.assertEqual(context.invoked_function_arn, "arn:test1") 40 | self.assertEqual(context.tenant_id, None) 41 | self.assertEqual(context.identity.cognito_identity_id, None) 42 | self.assertEqual(context.identity.cognito_identity_pool_id, None) 43 | self.assertEqual(context.client_context.client.installation_id, None) 44 | self.assertEqual(context.client_context.client.app_title, None) 45 | self.assertEqual(context.client_context.client.app_version_name, None) 46 | self.assertEqual(context.client_context.client.app_version_code, None) 47 | self.assertEqual(context.client_context.client.app_package_name, None) 48 | self.assertEqual(context.client_context.custom, None) 49 | self.assertEqual(context.client_context.env, None) 50 | 51 | def test_init_empty_env(self): 52 | client_context = {} 53 | cognito_identity = {} 54 | context = LambdaContext( 55 | "invoke-id1", client_context, cognito_identity, 1415836801000, "arn:test" 56 | ) 57 | 58 | self.assertEqual(context.log_group_name, None) 59 | self.assertEqual(context.log_stream_name, None) 60 | self.assertEqual(context.function_name, None) 61 | self.assertEqual(context.memory_limit_in_mb, None) 62 | self.assertEqual(context.function_version, None) 63 | self.assertEqual(context.client_context.client, None) 64 | 65 | def test_init_cognito(self): 66 | client_context = {} 67 | cognito_identity = { 68 | "cognitoIdentityId": "id1", 69 | "cognitoIdentityPoolId": "poolid1", 70 | } 71 | context = LambdaContext( 72 | "invoke-id1", client_context, cognito_identity, 1415836801000, "arn:test" 73 | ) 74 | 75 | self.assertEqual(context.identity.cognito_identity_id, "id1") 76 | self.assertEqual(context.identity.cognito_identity_pool_id, "poolid1") 77 | 78 | def test_init_tenant_id(self): 79 | client_context = {} 80 | cognito_identity = {} 81 | tenant_id = "blue" 82 | 83 | context = LambdaContext( 84 | "invoke-id1", 85 | client_context, 86 | cognito_identity, 87 | 1415836801000, 88 | "arn:test", 89 | tenant_id, 90 | ) 91 | self.assertEqual(context.tenant_id, "blue") 92 | 93 | def test_init_client_context(self): 94 | client_context = { 95 | "client": { 96 | "installation_id": "installid1", 97 | "app_title": "title1", 98 | "app_version_name": "name1", 99 | "app_version_code": "versioncode1", 100 | "app_package_name": "package1", 101 | }, 102 | "custom": {"custom-key": "custom-value"}, 103 | "env": {"custom-env": "custom-value"}, 104 | } 105 | cognito_identity = {} 106 | 107 | context = LambdaContext( 108 | "invoke-id1", client_context, cognito_identity, 1415836801000, "arn:test" 109 | ) 110 | 111 | self.assertEqual(context.client_context.client.installation_id, "installid1") 112 | self.assertEqual(context.client_context.client.app_title, "title1") 113 | self.assertEqual(context.client_context.client.app_version_name, "name1") 114 | self.assertEqual(context.client_context.client.app_version_code, "versioncode1") 115 | self.assertEqual(context.client_context.client.app_package_name, "package1") 116 | 117 | @patch("time.time") 118 | def test_get_remaining_time_in_millis(self, mock_time): 119 | deadline_epoch_ms = 1415836801003 120 | mock_time.return_value = (deadline_epoch_ms - 3000) / 1000 121 | 122 | context = LambdaContext("", {}, {}, deadline_epoch_ms, "") 123 | remaining_time_in_millis = context.get_remaining_time_in_millis() 124 | self.assertEqual(remaining_time_in_millis, 3000) 125 | 126 | @patch("time.time") 127 | def test_get_remaining_time_in_millis_not_less_then_zero(self, mock_time): 128 | deadline_epoch_ms = 1415836801000 129 | mock_time.return_value = (deadline_epoch_ms + 9000) / 1000 130 | 131 | context = LambdaContext("", {}, {}, deadline_epoch_ms, "") 132 | remaining_time_in_millis = context.get_remaining_time_in_millis() 133 | self.assertEqual(remaining_time_in_millis, 0) 134 | 135 | @patch("awslambdaric.lambda_context.logging") 136 | def test_log(self, mock_logging): 137 | mock_log_sink = MagicMock() 138 | 139 | mock_handler = MagicMock() 140 | mock_handler.log_sink = mock_log_sink 141 | 142 | mock_logger = MagicMock() 143 | mock_logger.handlers = [mock_handler] 144 | 145 | mock_logging.getLogger.return_value = mock_logger 146 | 147 | context = LambdaContext("", {}, {}, 12345678, "") 148 | 149 | context.log("YOLO!") 150 | mock_logging.getLogger.assert_called_once() 151 | 152 | mock_log_sink.log.assert_called_once_with("YOLO!") 153 | 154 | @patch("awslambdaric.lambda_context.logging", MagicMock()) 155 | @patch("awslambdaric.bootstrap.sys") 156 | def test_log_without_handlers(self, mock_sys): 157 | context = LambdaContext("", {}, {}, 12345678, "") 158 | 159 | context.log("YOLO!") 160 | 161 | mock_sys.stdout.write("YOLO!") 162 | -------------------------------------------------------------------------------- /tests/test_lambda_runtime_client.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | import http 6 | import http.client 7 | import unittest.mock 8 | from unittest.mock import MagicMock, patch 9 | 10 | from awslambdaric import __version__ 11 | from awslambdaric.lambda_runtime_client import ( 12 | InvocationRequest, 13 | LambdaRuntimeClient, 14 | LambdaRuntimeClientError, 15 | _user_agent, 16 | ) 17 | from awslambdaric.lambda_runtime_marshaller import to_json 18 | 19 | 20 | class TestInvocationRequest(unittest.TestCase): 21 | def test_constructor(self): 22 | invocation_request = InvocationRequest( 23 | invoke_id="Lambda-Runtime-Aws-Request-Id", 24 | x_amzn_trace_id="Lambda-Runtime-Trace-Id", 25 | invoked_function_arn="Lambda-Runtime-Invoked-Function-Arn", 26 | deadline_time_in_ms="Lambda-Runtime-Deadline-Ms", 27 | client_context="Lambda-Runtime-Client-Context", 28 | cognito_identity="Lambda-Runtime-Cognito-Identity", 29 | tenant_id="Lambda-Runtime-Aws-Tenant-Id", 30 | content_type="Content-Type", 31 | event_body="response_body", 32 | ) 33 | 34 | equal_invocation_request = InvocationRequest( 35 | invoke_id="Lambda-Runtime-Aws-Request-Id", 36 | x_amzn_trace_id="Lambda-Runtime-Trace-Id", 37 | invoked_function_arn="Lambda-Runtime-Invoked-Function-Arn", 38 | deadline_time_in_ms="Lambda-Runtime-Deadline-Ms", 39 | client_context="Lambda-Runtime-Client-Context", 40 | cognito_identity="Lambda-Runtime-Cognito-Identity", 41 | tenant_id="Lambda-Runtime-Aws-Tenant-Id", 42 | content_type="Content-Type", 43 | event_body="response_body", 44 | ) 45 | 46 | different_invocation_request = InvocationRequest( 47 | invoke_id="Lambda-Runtime-Aws-Request-Id", 48 | x_amzn_trace_id="Lambda-Runtime-Trace-Id", 49 | invoked_function_arn="Lambda-Runtime-Invoked-Function-Arn", 50 | deadline_time_in_ms="Lambda-Runtime-Deadline-Ms", 51 | client_context="Lambda-Runtime-Client-Context", 52 | cognito_identity="Lambda-Runtime-Cognito-Identity", 53 | tenant_id="Lambda-Runtime-Aws-Tenant-Id", 54 | content_type="Content-Type", 55 | event_body="another_response_body", 56 | ) 57 | 58 | self.assertTrue(invocation_request == invocation_request) 59 | self.assertTrue(invocation_request == equal_invocation_request) 60 | self.assertFalse(invocation_request == different_invocation_request) 61 | 62 | 63 | class TestLambdaRuntime(unittest.TestCase): 64 | @patch("awslambdaric.lambda_runtime_client.runtime_client") 65 | def test_wait_next_invocation(self, mock_runtime_client): 66 | response_body = b"{}" 67 | headears = { 68 | "Lambda-Runtime-Aws-Request-Id": "RID1234", 69 | "Lambda-Runtime-Trace-Id": "TID1234", 70 | "Lambda-Runtime-Invoked-Function-Arn": "FARN1234", 71 | "Lambda-Runtime-Deadline-Ms": 12, 72 | "Lambda-Runtime-Client-Context": "client_context", 73 | "Lambda-Runtime-Cognito-Identity": "cognito_identity", 74 | "Lambda-Runtime-Aws-Tenant-Id": "tenant_id", 75 | "Content-Type": "application/json", 76 | } 77 | mock_runtime_client.next.return_value = response_body, headears 78 | runtime_client = LambdaRuntimeClient("localhost:1234") 79 | 80 | event_request = runtime_client.wait_next_invocation() 81 | 82 | self.assertIsNotNone(event_request) 83 | self.assertEqual(event_request.invoke_id, "RID1234") 84 | self.assertEqual(event_request.x_amzn_trace_id, "TID1234") 85 | self.assertEqual(event_request.invoked_function_arn, "FARN1234") 86 | self.assertEqual(event_request.deadline_time_in_ms, 12) 87 | self.assertEqual(event_request.client_context, "client_context") 88 | self.assertEqual(event_request.cognito_identity, "cognito_identity") 89 | self.assertEqual(event_request.tenant_id, "tenant_id") 90 | self.assertEqual(event_request.content_type, "application/json") 91 | self.assertEqual(event_request.event_body, response_body) 92 | 93 | # Using ThreadPoolExecutor to polling next() 94 | runtime_client = LambdaRuntimeClient("localhost:1234", True) 95 | 96 | event_request = runtime_client.wait_next_invocation() 97 | 98 | self.assertIsNotNone(event_request) 99 | self.assertEqual(event_request.invoke_id, "RID1234") 100 | self.assertEqual(event_request.x_amzn_trace_id, "TID1234") 101 | self.assertEqual(event_request.invoked_function_arn, "FARN1234") 102 | self.assertEqual(event_request.deadline_time_in_ms, 12) 103 | self.assertEqual(event_request.client_context, "client_context") 104 | self.assertEqual(event_request.cognito_identity, "cognito_identity") 105 | self.assertEqual(event_request.tenant_id, "tenant_id") 106 | self.assertEqual(event_request.content_type, "application/json") 107 | self.assertEqual(event_request.event_body, response_body) 108 | 109 | @patch("awslambdaric.lambda_runtime_client.runtime_client") 110 | def test_wait_next_invocation_without_tenant_id_header(self, mock_runtime_client): 111 | response_body = b"{}" 112 | headers = { 113 | "Lambda-Runtime-Aws-Request-Id": "RID1234", 114 | "Lambda-Runtime-Trace-Id": "TID1234", 115 | "Lambda-Runtime-Invoked-Function-Arn": "FARN1234", 116 | "Lambda-Runtime-Deadline-Ms": 12, 117 | "Lambda-Runtime-Client-Context": "client_context", 118 | "Lambda-Runtime-Cognito-Identity": "cognito_identity", 119 | "Content-Type": "application/json", 120 | } 121 | mock_runtime_client.next.return_value = response_body, headers 122 | runtime_client = LambdaRuntimeClient("localhost:1234") 123 | 124 | event_request = runtime_client.wait_next_invocation() 125 | 126 | self.assertIsNotNone(event_request) 127 | self.assertIsNone(event_request.tenant_id) 128 | self.assertEqual(event_request.event_body, response_body) 129 | 130 | @patch("awslambdaric.lambda_runtime_client.runtime_client") 131 | def test_wait_next_invocation_with_null_tenant_id_header(self, mock_runtime_client): 132 | response_body = b"{}" 133 | headers = { 134 | "Lambda-Runtime-Aws-Request-Id": "RID1234", 135 | "Lambda-Runtime-Trace-Id": "TID1234", 136 | "Lambda-Runtime-Invoked-Function-Arn": "FARN1234", 137 | "Lambda-Runtime-Deadline-Ms": 12, 138 | "Lambda-Runtime-Client-Context": "client_context", 139 | "Lambda-Runtime-Cognito-Identity": "cognito_identity", 140 | "Lambda-Runtime-Aws-Tenant-Id": None, 141 | "Content-Type": "application/json", 142 | } 143 | mock_runtime_client.next.return_value = response_body, headers 144 | runtime_client = LambdaRuntimeClient("localhost:1234") 145 | 146 | event_request = runtime_client.wait_next_invocation() 147 | 148 | self.assertIsNotNone(event_request) 149 | self.assertIsNone(event_request.tenant_id) 150 | self.assertEqual(event_request.event_body, response_body) 151 | 152 | @patch("awslambdaric.lambda_runtime_client.runtime_client") 153 | def test_wait_next_invocation_with_empty_tenant_id_header( 154 | self, mock_runtime_client 155 | ): 156 | response_body = b"{}" 157 | headers = { 158 | "Lambda-Runtime-Aws-Request-Id": "RID1234", 159 | "Lambda-Runtime-Trace-Id": "TID1234", 160 | "Lambda-Runtime-Invoked-Function-Arn": "FARN1234", 161 | "Lambda-Runtime-Deadline-Ms": 12, 162 | "Lambda-Runtime-Client-Context": "client_context", 163 | "Lambda-Runtime-Cognito-Identity": "cognito_identity", 164 | "Lambda-Runtime-Aws-Tenant-Id": "", 165 | "Content-Type": "application/json", 166 | } 167 | mock_runtime_client.next.return_value = response_body, headers 168 | runtime_client = LambdaRuntimeClient("localhost:1234") 169 | 170 | event_request = runtime_client.wait_next_invocation() 171 | 172 | self.assertIsNotNone(event_request) 173 | self.assertEqual(event_request.tenant_id, "") 174 | self.assertEqual(event_request.event_body, response_body) 175 | 176 | error_result = { 177 | "errorMessage": "Dummy message", 178 | "errorType": "Runtime.DummyError", 179 | "requestId": "", 180 | "stackTrace": [], 181 | } 182 | 183 | headers = {"Lambda-Runtime-Function-Error-Type": error_result["errorType"]} 184 | 185 | restore_error_result = { 186 | "errorMessage": "Dummy Restore error", 187 | "errorType": "Runtime.DummyRestoreError", 188 | "requestId": "", 189 | "stackTrace": [], 190 | } 191 | 192 | restore_error_header = { 193 | "Lambda-Runtime-Function-Error-Type": "Runtime.AfterRestoreError" 194 | } 195 | 196 | before_snapshot_error_header = { 197 | "Lambda-Runtime-Function-Error-Type": "Runtime.BeforeSnapshotError" 198 | } 199 | 200 | @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) 201 | def test_post_init_error(self, MockHTTPConnection): 202 | mock_conn = MockHTTPConnection.return_value 203 | mock_response = MagicMock(autospec=http.client.HTTPResponse) 204 | mock_conn.getresponse.return_value = mock_response 205 | mock_response.read.return_value = b"" 206 | mock_response.code = http.HTTPStatus.ACCEPTED 207 | 208 | runtime_client = LambdaRuntimeClient("localhost:1234") 209 | runtime_client.post_init_error(self.error_result) 210 | 211 | MockHTTPConnection.assert_called_with("localhost:1234") 212 | mock_conn.request.assert_called_once_with( 213 | "POST", 214 | "/2018-06-01/runtime/init/error", 215 | to_json(self.error_result), 216 | headers=self.headers, 217 | ) 218 | mock_response.read.assert_called_once() 219 | 220 | @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) 221 | def test_post_init_error_non_accepted_status_code(self, MockHTTPConnection): 222 | mock_conn = MockHTTPConnection.return_value 223 | mock_response = MagicMock(autospec=http.client.HTTPResponse) 224 | mock_conn.getresponse.return_value = mock_response 225 | mock_response.read.return_value = b"" 226 | mock_response.code = http.HTTPStatus.IM_USED 227 | 228 | runtime_client = LambdaRuntimeClient("localhost:1234") 229 | 230 | with self.assertRaises(LambdaRuntimeClientError) as cm: 231 | runtime_client.post_init_error(self.error_result) 232 | returned_exception = cm.exception 233 | 234 | self.assertEqual(returned_exception.endpoint, "/2018-06-01/runtime/init/error") 235 | self.assertEqual(returned_exception.response_code, http.HTTPStatus.IM_USED) 236 | 237 | @patch("awslambdaric.lambda_runtime_client.runtime_client") 238 | def test_post_invocation_result(self, mock_runtime_client): 239 | runtime_client = LambdaRuntimeClient("localhost:1234") 240 | response_data = "data" 241 | invoke_id = "1234" 242 | 243 | runtime_client.post_invocation_result(invoke_id, response_data) 244 | 245 | mock_runtime_client.post_invocation_result.assert_called_once_with( 246 | invoke_id, response_data.encode("utf-8"), "application/json" 247 | ) 248 | 249 | @patch("awslambdaric.lambda_runtime_client.runtime_client") 250 | def test_post_invocation_result_binary_data(self, mock_runtime_client): 251 | runtime_client = LambdaRuntimeClient("localhost:1234") 252 | response_data = b"binary_data" 253 | invoke_id = "1234" 254 | content_type = "application/octet-stream" 255 | 256 | runtime_client.post_invocation_result(invoke_id, response_data, content_type) 257 | 258 | mock_runtime_client.post_invocation_result.assert_called_once_with( 259 | invoke_id, response_data, content_type 260 | ) 261 | 262 | @patch("awslambdaric.lambda_runtime_client.runtime_client") 263 | def test_post_invocation_result_failure(self, mock_runtime_client): 264 | runtime_client = LambdaRuntimeClient("localhost:1234") 265 | response_data = "data" 266 | invoke_id = "1234" 267 | 268 | mock_runtime_client.post_invocation_result.side_effect = RuntimeError( 269 | "Failed to post invocation response" 270 | ) 271 | 272 | with self.assertRaisesRegex(RuntimeError, "Failed to post invocation response"): 273 | runtime_client.post_invocation_result(invoke_id, response_data) 274 | 275 | @patch("awslambdaric.lambda_runtime_client.runtime_client") 276 | def test_post_invocation_error(self, mock_runtime_client): 277 | runtime_client = LambdaRuntimeClient("localhost:1234") 278 | error_data = "data" 279 | invoke_id = "1234" 280 | xray_fault = "xray_fault" 281 | 282 | runtime_client.post_invocation_error(invoke_id, error_data, xray_fault) 283 | 284 | mock_runtime_client.post_error.assert_called_once_with( 285 | invoke_id, error_data, xray_fault 286 | ) 287 | 288 | @patch("awslambdaric.lambda_runtime_client.runtime_client") 289 | def test_post_invocation_error_with_large_xray_cause(self, mock_runtime_client): 290 | runtime_client = LambdaRuntimeClient("localhost:1234") 291 | error_data = "data" 292 | invoke_id = "1234" 293 | large_xray_fault = ("a" * int(1024 * 1024))[:-1] 294 | 295 | runtime_client.post_invocation_error(invoke_id, error_data, large_xray_fault) 296 | 297 | mock_runtime_client.post_error.assert_called_once_with( 298 | invoke_id, error_data, large_xray_fault 299 | ) 300 | 301 | @patch("awslambdaric.lambda_runtime_client.runtime_client") 302 | def test_post_invocation_error_with_too_large_xray_cause(self, mock_runtime_client): 303 | runtime_client = LambdaRuntimeClient("localhost:1234") 304 | error_data = "data" 305 | invoke_id = "1234" 306 | too_large_xray_fault = "a" * int(1024 * 1024) 307 | 308 | runtime_client.post_invocation_error( 309 | invoke_id, error_data, too_large_xray_fault 310 | ) 311 | 312 | mock_runtime_client.post_error.assert_called_once_with( 313 | invoke_id, error_data, "" 314 | ) 315 | 316 | @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) 317 | def test_restore_next(self, MockHTTPConnection): 318 | mock_conn = MockHTTPConnection.return_value 319 | mock_response = MagicMock(autospec=http.client.HTTPResponse) 320 | mock_conn.getresponse.return_value = mock_response 321 | mock_response.read.return_value = b"" 322 | mock_response.code = http.HTTPStatus.OK 323 | 324 | runtime_client = LambdaRuntimeClient("localhost:1234") 325 | runtime_client.restore_next() 326 | 327 | MockHTTPConnection.assert_called_with("localhost:1234") 328 | mock_conn.request.assert_called_once_with( 329 | "GET", 330 | "/2018-06-01/runtime/restore/next", 331 | ) 332 | mock_response.read.assert_called_once() 333 | 334 | @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) 335 | def test_restore_error(self, MockHTTPConnection): 336 | mock_conn = MockHTTPConnection.return_value 337 | mock_response = MagicMock(autospec=http.client.HTTPResponse) 338 | mock_conn.getresponse.return_value = mock_response 339 | mock_response.read.return_value = b"" 340 | mock_response.code = http.HTTPStatus.ACCEPTED 341 | 342 | runtime_client = LambdaRuntimeClient("localhost:1234") 343 | runtime_client.report_restore_error(self.restore_error_result) 344 | 345 | MockHTTPConnection.assert_called_with("localhost:1234") 346 | mock_conn.request.assert_called_once_with( 347 | "POST", 348 | "/2018-06-01/runtime/restore/error", 349 | to_json(self.restore_error_result), 350 | headers=self.restore_error_header, 351 | ) 352 | mock_response.read.assert_called_once() 353 | 354 | @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) 355 | def test_init_before_snapshot_error(self, MockHTTPConnection): 356 | mock_conn = MockHTTPConnection.return_value 357 | mock_response = MagicMock(autospec=http.client.HTTPResponse) 358 | mock_conn.getresponse.return_value = mock_response 359 | mock_response.read.return_value = b"" 360 | mock_response.code = http.HTTPStatus.ACCEPTED 361 | 362 | runtime_client = LambdaRuntimeClient("localhost:1234") 363 | runtime_client.post_init_error(self.error_result, "Runtime.BeforeSnapshotError") 364 | 365 | MockHTTPConnection.assert_called_with("localhost:1234") 366 | mock_conn.request.assert_called_once_with( 367 | "POST", 368 | "/2018-06-01/runtime/init/error", 369 | to_json(self.error_result), 370 | headers=self.before_snapshot_error_header, 371 | ) 372 | mock_response.read.assert_called_once() 373 | 374 | def test_connection_refused(self): 375 | with self.assertRaises(ConnectionRefusedError): 376 | runtime_client = LambdaRuntimeClient("127.0.0.1:1") 377 | runtime_client.post_init_error(self.error_result) 378 | 379 | def test_invalid_addr(self): 380 | with self.assertRaises(OSError): 381 | runtime_client = LambdaRuntimeClient("::::") 382 | runtime_client.post_init_error(self.error_result) 383 | 384 | def test_lambdaric_version(self): 385 | self.assertTrue(_user_agent().endswith(__version__)) 386 | 387 | 388 | class TestLambdaRuntimeClientError(unittest.TestCase): 389 | def test_constructor(self): 390 | expected_endpoint = "" 391 | expected_response_code = "" 392 | expected_response_body = "" 393 | 394 | lambda_runtime_client_error = LambdaRuntimeClientError( 395 | expected_endpoint, expected_response_code, expected_response_body 396 | ) 397 | 398 | self.assertIsInstance(lambda_runtime_client_error, Exception) 399 | self.assertEqual(lambda_runtime_client_error.endpoint, expected_endpoint) 400 | 401 | 402 | if __name__ == "__main__": 403 | unittest.main() 404 | -------------------------------------------------------------------------------- /tests/test_lambda_runtime_marshaller.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | import decimal 6 | import os 7 | import unittest 8 | from parameterized import parameterized 9 | from awslambdaric.lambda_runtime_marshaller import to_json 10 | 11 | 12 | class TestLambdaRuntimeMarshaller(unittest.TestCase): 13 | execution_envs = ( 14 | "AWS_Lambda_python3.13", 15 | "AWS_Lambda_python3.12", 16 | "AWS_Lambda_python3.11", 17 | "AWS_Lambda_python3.10", 18 | "AWS_Lambda_python3.9", 19 | ) 20 | 21 | envs_lambda_marshaller_ensure_ascii_false = { 22 | "AWS_Lambda_python3.12", 23 | "AWS_Lambda_python3.13", 24 | } 25 | 26 | execution_envs_lambda_marshaller_ensure_ascii_true = tuple( 27 | set(execution_envs).difference(envs_lambda_marshaller_ensure_ascii_false) 28 | ) 29 | execution_envs_lambda_marshaller_ensure_ascii_false = tuple( 30 | envs_lambda_marshaller_ensure_ascii_false 31 | ) 32 | 33 | def setUp(self): 34 | self.org_os_environ = os.environ 35 | 36 | def tearDown(self): 37 | os.environ = self.org_os_environ 38 | 39 | def test_to_json_decimal_encoding(self): 40 | response = to_json({"pi": decimal.Decimal("3.14159")}) 41 | self.assertEqual('{"pi": 3.14159}', response) 42 | 43 | def test_to_json_decimal_encoding_nan(self): 44 | response = to_json({"pi": decimal.Decimal("nan")}) 45 | self.assertEqual('{"pi": NaN}', response) 46 | 47 | def test_to_json_decimal_encoding_negative_nan(self): 48 | response = to_json({"pi": decimal.Decimal("-nan")}) 49 | self.assertEqual('{"pi": NaN}', response) 50 | 51 | def test_json_serializer_is_not_default_json(self): 52 | from awslambdaric.lambda_runtime_marshaller import ( 53 | json as internal_json, 54 | ) 55 | import simplejson as simplejson 56 | import json as stock_json 57 | import json 58 | 59 | self.assertEqual(json, stock_json) 60 | self.assertNotEqual(stock_json, internal_json) 61 | self.assertNotEqual(stock_json, simplejson) 62 | 63 | internal_json.YOLO = "bello" 64 | self.assertTrue(hasattr(internal_json, "YOLO")) 65 | self.assertFalse(hasattr(stock_json, "YOLO")) 66 | self.assertTrue(hasattr(simplejson, "YOLO")) 67 | 68 | @parameterized.expand(execution_envs_lambda_marshaller_ensure_ascii_false) 69 | def test_to_json_unicode_not_escaped_encoding(self, execution_env): 70 | os.environ = {"AWS_EXECUTION_ENV": execution_env} 71 | response = to_json({"price": "£1.00"}) 72 | self.assertEqual('{"price": "£1.00"}', response) 73 | self.assertNotEqual('{"price": "\\u00a31.00"}', response) 74 | self.assertEqual( 75 | 19, len(response.encode("utf-8")) 76 | ) # would be 23 bytes if a unicode escape was returned 77 | 78 | @parameterized.expand(execution_envs_lambda_marshaller_ensure_ascii_true) 79 | def test_to_json_unicode_is_escaped_encoding(self, execution_env): 80 | os.environ = {"AWS_EXECUTION_ENV": execution_env} 81 | response = to_json({"price": "£1.00"}) 82 | self.assertEqual('{"price": "\\u00a31.00"}', response) 83 | self.assertNotEqual('{"price": "£1.00"}', response) 84 | self.assertEqual( 85 | 23, len(response.encode("utf-8")) 86 | ) # would be 19 bytes if a escaped was returned 87 | -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | """ 4 | 5 | import os 6 | import unittest 7 | from unittest.mock import patch 8 | 9 | import awslambdaric.__main__ as package_entry 10 | 11 | 12 | class TestEnvVars(unittest.TestCase): 13 | def setUp(self): 14 | self.org_os_environ = os.environ 15 | 16 | def tearDown(self): 17 | os.environ = self.org_os_environ 18 | 19 | @patch("awslambdaric.__main__.bootstrap") 20 | def test_main(self, mock_bootstrap): 21 | expected_app_root = os.getcwd() 22 | expected_handler = "app.my_test_handler" 23 | expected_lambda_runtime_api_addr = "test_addr" 24 | 25 | args = ["dummy", expected_handler, "other_dummy"] 26 | 27 | os.environ["AWS_LAMBDA_RUNTIME_API"] = expected_lambda_runtime_api_addr 28 | 29 | package_entry.main(args) 30 | 31 | mock_bootstrap.run.assert_called_once_with( 32 | expected_app_root, expected_handler, expected_lambda_runtime_api_addr 33 | ) 34 | -------------------------------------------------------------------------------- /tests/test_runtime_hooks.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import unittest 5 | from unittest.mock import patch, call 6 | from awslambdaric import lambda_runtime_hooks_runner 7 | import snapshot_restore_py 8 | 9 | 10 | def fun_test1(): 11 | print("In function ONE") 12 | 13 | 14 | def fun_test2(): 15 | print("In function TWO") 16 | 17 | 18 | def fun_with_args_kwargs(x, y, **kwargs): 19 | print("Here are the args:", x, y) 20 | print("Here are the keyword args:", kwargs) 21 | 22 | 23 | class TestRuntimeHooks(unittest.TestCase): 24 | def tearDown(self): 25 | # We are accessing private filed for cleaning up 26 | snapshot_restore_py._before_snapshot_registry = [] 27 | snapshot_restore_py._after_restore_registry = [] 28 | 29 | @patch("builtins.print") 30 | def test_before_snapshot_execution_order(self, mock_print): 31 | snapshot_restore_py.register_before_snapshot( 32 | fun_with_args_kwargs, 5, 7, arg1="Lambda", arg2="SnapStart" 33 | ) 34 | snapshot_restore_py.register_before_snapshot(fun_test2) 35 | snapshot_restore_py.register_before_snapshot(fun_test1) 36 | 37 | lambda_runtime_hooks_runner.run_before_snapshot() 38 | 39 | calls = [] 40 | calls.append(call("In function ONE")) 41 | calls.append(call("In function TWO")) 42 | calls.append(call("Here are the args:", 5, 7)) 43 | calls.append( 44 | call("Here are the keyword args:", {"arg1": "Lambda", "arg2": "SnapStart"}) 45 | ) 46 | self.assertEqual(calls, mock_print.mock_calls) 47 | 48 | @patch("builtins.print") 49 | def test_after_restore_execution_order(self, mock_print): 50 | snapshot_restore_py.register_after_restore( 51 | fun_with_args_kwargs, 11, 13, arg1="Lambda", arg2="SnapStart" 52 | ) 53 | snapshot_restore_py.register_after_restore(fun_test2) 54 | snapshot_restore_py.register_after_restore(fun_test1) 55 | 56 | lambda_runtime_hooks_runner.run_after_restore() 57 | 58 | calls = [] 59 | calls.append(call("Here are the args:", 11, 13)) 60 | calls.append( 61 | call("Here are the keyword args:", {"arg1": "Lambda", "arg2": "SnapStart"}) 62 | ) 63 | calls.append(call("In function TWO")) 64 | calls.append(call("In function ONE")) 65 | self.assertEqual(calls, mock_print.mock_calls) 66 | --------------------------------------------------------------------------------