diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2a20b40..27c4733 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,6 +58,7 @@ jobs: - "python310" - "python311" - "python312" + - "python313" - "ruby32" - "ruby33" include: diff --git a/Makefile b/Makefile index 0ff5e60..d9e3c73 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ IS_python39 := python3.9 IS_python310 := python3.10 IS_python311 := python3.11 IS_python312 := python3.12 +IS_python313 := python3.13 IS_ruby32 := ruby3.2 IS_ruby33 := ruby3.3 diff --git a/build-image-src/Dockerfile-python313 b/build-image-src/Dockerfile-python313 new file mode 100644 index 0000000..bae2841 --- /dev/null +++ b/build-image-src/Dockerfile-python313 @@ -0,0 +1,58 @@ +ARG IMAGE_ARCH +FROM public.ecr.aws/lambda/python:3.13-$IMAGE_ARCH + +RUN dnf remove -y microdnf-dnf && \ + microdnf install -y dnf + +RUN dnf groupinstall -y development && \ + dnf install -y \ + tar \ + gzip \ + unzip \ + python3 \ + jq \ + grep \ + make \ + rsync \ + binutils \ + gcc-c++ \ + procps \ + gmp-devel \ + zlib-devel \ + libmpc-devel \ + python3-devel \ + && dnf clean all + +# Install AWS CLI +ARG AWS_CLI_ARCH +RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-$AWS_CLI_ARCH.zip" -o "awscliv2.zip" && unzip awscliv2.zip && ./aws/install && rm awscliv2.zip && rm -rf ./aws + +# Install SAM CLI from native installer +ARG SAM_CLI_VERSION +# need to redefine since ARG is not available after FROM tag: https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact +ARG IMAGE_ARCH +RUN curl -L "https://github.com/aws/aws-sam-cli/releases/download/v$SAM_CLI_VERSION/aws-sam-cli-linux-$IMAGE_ARCH.zip" -o "samcli.zip" && \ + unzip samcli.zip -d sam-installation && ./sam-installation/install && \ + rm samcli.zip && rm -rf sam-installation && sam --version + +# Prepare virtualenv for lambda builders +RUN python3 -m venv --without-pip /usr/local/opt/lambda-builders +RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py +RUN /usr/local/opt/lambda-builders/bin/python3 get-pip.py +# Install lambda builders in a dedicated Python virtualenv +RUN AWS_LB_VERSION=$(curl -sSL https://raw.githubusercontent.com/aws/aws-sam-cli/v$SAM_CLI_VERSION/requirements/base.txt | grep aws_lambda_builders | cut -d= -f3) && \ + /usr/local/opt/lambda-builders/bin/pip3 --no-cache-dir install "aws-lambda-builders==$AWS_LB_VERSION" + +ENV PATH=$PATH:/usr/local/opt/lambda-builders/bin + +ENV LANG=en_US.UTF-8 + +# Wheel is required by SAM CLI to build libraries like cryptography. It needs to be installed in the system +# Python for it to be picked up during `sam build` +RUN pip3 install wheel + +COPY ATTRIBUTION.txt / + +# Compatible with initial base image +ENTRYPOINT [] +CMD ["/bin/bash"] diff --git a/build-image-src/build_all_images.sh b/build-image-src/build_all_images.sh index df0a0ec..acc59b6 100755 --- a/build-image-src/build_all_images.sh +++ b/build-image-src/build_all_images.sh @@ -36,6 +36,7 @@ docker build -f Dockerfile-python39 -t amazon/aws-sam-cli-build-image-python3.9: docker build -f Dockerfile-python310 -t amazon/aws-sam-cli-build-image-python3.10:x86_64 --platform linux/amd64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=x86_64 --build-arg IMAGE_ARCH=x86_64 . & docker build -f Dockerfile-python311 -t amazon/aws-sam-cli-build-image-python3.11:x86_64 --platform linux/amd64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=x86_64 --build-arg IMAGE_ARCH=x86_64 . & docker build -f Dockerfile-python312 -t amazon/aws-sam-cli-build-image-python3.12:x86_64 --platform linux/amd64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=x86_64 --build-arg IMAGE_ARCH=x86_64 . & +docker build -f Dockerfile-python313 -t amazon/aws-sam-cli-build-image-python3.13:x86_64 --platform linux/amd64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=x86_64 --build-arg IMAGE_ARCH=x86_64 . & docker build -f Dockerfile-ruby32 -t amazon/aws-sam-cli-build-image-ruby3.2:x86_64 --platform linux/amd64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=x86_64 --build-arg IMAGE_ARCH=x86_64 . & docker build -f Dockerfile-ruby33 -t amazon/aws-sam-cli-build-image-ruby3.3:x86_64 --platform linux/amd64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=x86_64 --build-arg IMAGE_ARCH=x86_64 . & wait @@ -59,6 +60,7 @@ docker build -f Dockerfile-python39 -t amazon/aws-sam-cli-build-image-python3.9: docker build -f Dockerfile-python310 -t amazon/aws-sam-cli-build-image-python3.10:arm64 --platform linux/arm64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=aarch64 --build-arg IMAGE_ARCH=arm64 . & docker build -f Dockerfile-python311 -t amazon/aws-sam-cli-build-image-python3.11:arm64 --platform linux/arm64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=aarch64 --build-arg IMAGE_ARCH=arm64 . & docker build -f Dockerfile-python312 -t amazon/aws-sam-cli-build-image-python3.12:arm64 --platform linux/arm64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=aarch64 --build-arg IMAGE_ARCH=arm64 . & +docker build -f Dockerfile-python313 -t amazon/aws-sam-cli-build-image-python3.13:arm64 --platform linux/arm64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=aarch64 --build-arg IMAGE_ARCH=arm64 . & docker build -f Dockerfile-ruby32 -t amazon/aws-sam-cli-build-image-ruby3.2:arm64 --platform linux/arm64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=aarch64 --build-arg IMAGE_ARCH=arm64 . & docker build -f Dockerfile-ruby33 -t amazon/aws-sam-cli-build-image-ruby3.3:arm64 --platform linux/arm64 --build-arg SAM_CLI_VERSION=$SAM_CLI_VERSION --build-arg AWS_CLI_ARCH=aarch64 --build-arg IMAGE_ARCH=arm64 . & wait diff --git a/pytest.ini b/pytest.ini index 990a1ec..a0c1f87 100644 --- a/pytest.ini +++ b/pytest.ini @@ -19,5 +19,6 @@ markers = python310 python311 python312 + python313 ruby32 ruby33 diff --git a/tests/apps/python3.13/.gitignore b/tests/apps/python3.13/.gitignore new file mode 100644 index 0000000..4808264 --- /dev/null +++ b/tests/apps/python3.13/.gitignore @@ -0,0 +1,244 @@ + +# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Build folder + +*/build/* + +# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode \ No newline at end of file diff --git a/tests/apps/python3.13/__init__.py b/tests/apps/python3.13/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/apps/python3.13/events/event.json b/tests/apps/python3.13/events/event.json new file mode 100644 index 0000000..070ad8e --- /dev/null +++ b/tests/apps/python3.13/events/event.json @@ -0,0 +1,62 @@ +{ + "body": "{\"message\": \"hello world\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} diff --git a/tests/apps/python3.13/hello_world/__init__.py b/tests/apps/python3.13/hello_world/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/apps/python3.13/hello_world/app.py b/tests/apps/python3.13/hello_world/app.py new file mode 100644 index 0000000..139af7d --- /dev/null +++ b/tests/apps/python3.13/hello_world/app.py @@ -0,0 +1,44 @@ +import json + +# import requests + + +def lambda_handler(event, context): + """Sample pure Lambda function + + Parameters + ---------- + event: dict, required + API Gateway Lambda Proxy Input Format + + Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + + context: object, required + Lambda Context runtime methods and attributes + + Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html + + Returns + ------ + API Gateway Lambda Proxy Output Format: dict + + Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html + """ + + # try: + # ip = requests.get("http://checkip.amazonaws.com/") + # except requests.RequestException as e: + # # Send some context about this error to Lambda Logs + # print(e) + + # raise e + + return { + "statusCode": 200, + "body": json.dumps( + { + "message": "hello world", + # "location": ip.text.replace("\n", "") + } + ), + } diff --git a/tests/apps/python3.13/hello_world/requirements.txt b/tests/apps/python3.13/hello_world/requirements.txt new file mode 100644 index 0000000..663bd1f --- /dev/null +++ b/tests/apps/python3.13/hello_world/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file diff --git a/tests/apps/python3.13/template.yaml b/tests/apps/python3.13/template.yaml new file mode 100644 index 0000000..578f372 --- /dev/null +++ b/tests/apps/python3.13/template.yaml @@ -0,0 +1,39 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + sam-test-app9 + + Sample SAM Template for sam-test-app9 + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 3 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: hello_world/ + Handler: app.lambda_handler + Runtime: python3.13 + Events: + HelloWorld: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /hello + Method: get + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + HelloWorldApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + HelloWorldFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt HelloWorldFunction.Arn + HelloWorldFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt HelloWorldFunctionRole.Arn diff --git a/tests/test_build_images.py b/tests/test_build_images.py index 0ff73b9..bcd605d 100644 --- a/tests/test_build_images.py +++ b/tests/test_build_images.py @@ -485,6 +485,37 @@ def test_packages(self): self.assertTrue(self.check_package_output("python --version", "Python 3.12.")) self.assertTrue(self.is_package_present("pip")) +@pytest.mark.python313arm64 +class TestBIPython313ForArm(AL2023BasedBuildImageBase): + __test__ = True + + @classmethod + def setUpClass(cls): + super().setUpClass("python3.13", "Dockerfile-python313", "pip", tag="arm64") + + def test_packages(self): + """ + Test packages specific to this build image + """ + self.assertTrue(self.check_package_output("python --version", "Python 3.13.")) + self.assertTrue(self.is_package_present("pip")) + + +@pytest.mark.python313x86_64 +class TestBIPython313(AL2023BasedBuildImageBase): + __test__ = True + + @classmethod + def setUpClass(cls): + super().setUpClass("python3.13", "Dockerfile-python313", "pip", tag="x86_64") + + def test_packages(self): + """ + Test packages specific to this build image + """ + self.assertTrue(self.check_package_output("python --version", "Python 3.13.")) + self.assertTrue(self.is_package_present("pip")) + @pytest.mark.python39arm64 class TestBIPython39ForArm(BuildImageBase):