├── .dockerignore ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── codeql-analysis.yml │ └── release.yml ├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ └── maven-wrapper.properties ├── CODE_OF_CONDUCT.md ├── HELP.md ├── LICENSE ├── NOTICE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── cdk.json ├── docker-compose.yml ├── lombok.config ├── mvnw ├── mvnw.cmd ├── pom.xml ├── spring-native-aws-lambda-function ├── pom.xml └── src │ ├── assembly │ └── native.xml │ ├── main │ ├── java │ │ └── com │ │ │ └── coffeebeans │ │ │ └── springnativeawslambda │ │ │ ├── Application.java │ │ │ ├── LambdaExceptionHandler.java │ │ │ ├── ReflectionRuntimeHints.java │ │ │ ├── ResourcesRuntimeHints.java │ │ │ ├── function │ │ │ └── ExampleFunction.java │ │ │ └── model │ │ │ ├── Request.java │ │ │ └── Response.java │ └── resources │ │ ├── application-local.yml │ │ └── application.yml │ ├── shell │ └── native │ │ └── bootstrap │ └── test │ └── java │ └── com │ └── coffeebeans │ └── springnativeawslambda │ ├── ApplicationIT.java │ └── function │ └── ExampleFunctionTest.java └── spring-native-aws-lambda-infra ├── pom.xml └── src ├── main └── java │ └── com │ └── coffeebeans │ └── springnativeawslambda │ └── infra │ ├── ApiBaseStack.java │ ├── Application.java │ ├── CoffeeBeansConstruct.java │ ├── Constants.java │ ├── SpringNativeAwsLambdaStack.java │ ├── StackUtils.java │ ├── TagUtils.java │ └── lambda │ └── CustomRuntime2023Function.java └── test ├── java └── com │ └── coffeebeans │ └── springnativeawslambda │ └── infra │ ├── ApiBaseStackTest.java │ ├── LambdaTest.java │ ├── RestApiTest.java │ ├── TagUtilsTest.java │ ├── TemplateSupport.java │ ├── TestLambdaUtils.java │ ├── TopicTest.java │ └── lambda │ └── CustomRuntime2023FunctionTest.java └── resources └── mockito-extensions └── org.mockito.plugins.MockMaker /.dockerignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .idea/ 3 | badges/ 4 | **/target/ 5 | cdk.out/ 6 | volume/ 7 | .gitignore 8 | CODE_OF_CONDUCT.md 9 | HELP.md 10 | LICENSE 11 | NOTICE 12 | PULL_REQUEST_TEMPLATE.md 13 | README.md 14 | **/*.iml 15 | 16 | settings-spring.xml 17 | 18 | spring-native-aws-lambda-infra 19 | docker-compose.yml 20 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @muhamadto 2 | * @andrew2184 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Versions** 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Licensed to Muhammad Hamadto 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | # See the NOTICE file distributed with this work for additional information regarding copyright ownership. 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | name: "Build" 17 | 18 | on: 19 | push: 20 | branches: [ "*" ] 21 | pull_request: 22 | branches: [ "main" ] 23 | types: [ opened, synchronize, reopened ] 24 | 25 | jobs: 26 | build: 27 | runs-on: ubuntu-latest 28 | strategy: 29 | matrix: 30 | java-version: [ '21' ] 31 | graalvm-version: [ '21.0.1' ] 32 | distribution: [ 'graalvm' ] 33 | fail-fast: true 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v3 37 | - name: Setup Java 38 | uses: graalvm/setup-graalvm@v1 39 | with: 40 | distribution: '${{ matrix.distribution }}' 41 | version: '${{ matrix.graalvm-version }}' 42 | java-version: '${{ matrix.java-version }}' 43 | cache: 'maven' 44 | - name: Cache Sonar packages 45 | uses: actions/cache@v3 46 | with: 47 | path: ~/.sonar/cache 48 | key: ${{ runner.os }}-sonar 49 | restore-keys: ${{ runner.os }}-sonar 50 | - name: Cache Maven packages 51 | uses: actions/cache@v3 52 | with: 53 | path: ~/.m2 54 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 55 | restore-keys: ${{ runner.os }}-m2 56 | - name: Build with Maven 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 60 | run: mvn --no-transfer-progress clean verify -Pnative org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=muhamadto_spring-native-aws-lambda 61 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # Licensed to Muhammad Hamadto 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | # See the NOTICE file distributed with this work for additional information regarding copyright ownership. 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | name: "CodeQL" 17 | 18 | on: 19 | push: 20 | branches: [ "*" ] 21 | pull_request: 22 | branches: [ "main" ] 23 | types: [ opened, synchronize, reopened ] 24 | 25 | jobs: 26 | analyze: 27 | name: Analyze 28 | runs-on: ubuntu-latest 29 | permissions: 30 | actions: read 31 | contents: read 32 | security-events: write 33 | strategy: 34 | matrix: 35 | java-version: [ '21' ] 36 | distribution: [ 'temurin' ] 37 | language: [ 'java' ] 38 | fail-fast: true 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | - name: Setup Java 43 | uses: actions/setup-java@v3 44 | with: 45 | distribution: '${{ matrix.distribution }}' 46 | java-version: '${{ matrix.java-version }}' 47 | cache: 'maven' 48 | - name: Initialize CodeQL 49 | uses: github/codeql-action/init@v2 50 | with: 51 | languages: ${{ matrix.language }} 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v2 54 | - name: Perform CodeQL Analysis 55 | uses: github/codeql-action/analyze@v2 56 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Licensed to Muhammad Hamadto 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | # See the NOTICE file distributed with this work for additional information regarding copyright ownership. 9 | 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | name: "Deploy to AWS" 17 | #run-name: Deploy to ${{ inputs.deploy_target }} by @${{ github.actor }} 18 | 19 | on: 20 | release: 21 | types: [ published ] 22 | 23 | jobs: 24 | release: 25 | runs-on: ubuntu-latest 26 | container: 27 | image: ghcr.io/muhamadto/spring-native-amazonlinux2-builder:21-amazonlinux2 28 | options: --user=worker:ci 29 | permissions: 30 | id-token: write 31 | contents: read 32 | env: 33 | ENV: dev 34 | COST_CENTRE: coffeebeans-core 35 | AWS_DEFAULT_REGION: 'ap-southeast-2' 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | steps: 38 | - name: Configure AWS credentials 39 | uses: aws-actions/configure-aws-credentials@master 40 | with: 41 | role-to-assume: ${{ secrets.ROLE_ARN }} 42 | duration_seconds: 3600 43 | role-session-name: github-actions-example-lambda 44 | aws-region: ap-southeast-2 45 | mask-aws-account-id: 'true' 46 | - name: Checkout repository 47 | uses: actions/checkout@v3 48 | - name: Cache Maven packages 49 | uses: actions/cache@v3 50 | with: 51 | path: ~/.m2 52 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 53 | restore-keys: ${{ runner.os }}-m2 54 | - name: Build with Maven 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 58 | ENV: ${{ env.ENV }} 59 | COST_CENTRE: ${{ env.COST_CENTRE }} 60 | run: | 61 | ./mvnw -ntp clean verify -DskipTests -pl spring-native-aws-lambda-infra 62 | ./mvnw -ntp clean -Pnative -DskipTests native:compile package -pl spring-native-aws-lambda-function 63 | - name: cdk diff 64 | uses: muhamadto/aws-cdk-github-actions@v5 65 | with: 66 | cdk_subcommand: 'diff' 67 | actions_comment: false 68 | - name: cdk deploy 69 | uses: muhamadto/aws-cdk-github-actions@v5 70 | with: 71 | cdk_subcommand: 'deploy' 72 | cdk_args: '--require-approval never' 73 | actions_comment: false 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | cdk.out/ 3 | volume/ 4 | **/maven-wrapper.jar 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.6"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to Muhammad Hamadto 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # 18 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip 19 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 20 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | muhamadto@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /HELP.md: -------------------------------------------------------------------------------- 1 | # Read Me First 2 | 3 | # Getting Started 4 | 5 | ### Reference Documentation 6 | 7 | For further reference, please consider the following sections: 8 | 9 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 10 | * [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.4.5/maven-plugin/reference/html/) 11 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.4.5/maven-plugin/reference/html/#build-image) 12 | * [Function](https://cloud.spring.io/spring-cloud-function/) 13 | 14 | ### Additional Links 15 | 16 | These additional references should also help you: 17 | 18 | * [Various sample apps using Spring Cloud Function](https://github.com/spring-cloud/spring-cloud-function/tree/master/spring-cloud-function-samples) 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Licensed to Muhammad Hamadto 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | See the NOTICE file distributed with this work for additional information regarding copyright ownership. 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Apache [spring-native-aws-lambda-function] 2 | Copyright 2021 Muhammad Hamadto 3 | 4 | This product includes software developed by: 5 | * Spring Initializr project https://start.spring.io/, as well as 6 | * spring-projects-experimental - https://github.com/spring-projects-experimental/spring-native/tree/main/samples/cloud-function-aws 7 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Hello, Please Review My Pull Request! 2 | 3 | 5 | 6 | #### :heavy_check_mark: Checklist 7 | 8 | 9 | 10 | - [ ] Describe what you did in the pull request description 11 | - [ ] Add Unit and Integration Tests - at least 80% unit tests for new code. 12 | - [ ] Added or updated documentation -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-native-aws-lambda 2 | 3 | [![CodeQL](https://github.com/muhamadto/spring-native-aws-lambda/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/muhamadto/spring-native-aws-lambda/actions/workflows/codeql-analysis.yml) 4 | [![Build](https://github.com/muhamadto/spring-native-aws-lambda/actions/workflows/build.yml/badge.svg)](https://github.com/muhamadto/spring-native-aws-lambda/actions/workflows/build.yml) 5 | [![Deploy to AWS](https://github.com/muhamadto/spring-native-aws-lambda/actions/workflows/release.yml/badge.svg?branch=feature-java17-cdk)](https://github.com/muhamadto/spring-native-aws-lambda/actions/workflows/release.yml) 6 | 7 | [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=muhamadto_spring-native-aws-lambda&metric=alert_status)](https://sonarcloud.io/dashboard?id=muhamadto_spring-native-aws-lambda) 8 | [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=muhamadto_spring-native-aws-lambda&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=muhamadto_spring-native-aws-lambda) 9 | [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=muhamadto_spring-native-aws-lambda&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=muhamadto_spring-native-aws-lambda) 10 | [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=muhamadto_spring-native-aws-lambda&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=muhamadto_spring-native-aws-lambda) 11 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=muhamadto_spring-native-aws-lambda&metric=bugs)](https://sonarcloud.io/summary/new_code?id=muhamadto_spring-native-aws-lambda) 12 | [![SonarCloud Coverage](https://sonarcloud.io/api/project_badges/measure?project=muhamadto_spring-native-aws-lambda&metric=coverage)](https://sonarcloud.io/component_measures?id=muhamadto_spring-native-aws-lambda&metric=new_coverage&view=list) 13 | 14 | | Component | Version | 15 | |--------------|----------| 16 | | JDK | 21 | 17 | | Spring Cloud | 2023.0.0 | 18 | | Spring Boot | 3.2.1 | 19 | 20 | ## Test 21 | 22 | ```bash 23 | $ sdk use java 21.0.1-graal 24 | $ ./mvnw -ntp clean verify -U 25 | ``` 26 | 27 | ## Building and Running 28 | 29 | ### Locally 30 | 31 | #### Using `docker-compose` 32 | 1. Run the following commands 33 | ```shell 34 | $ docker-compose up 35 | ``` 36 | 2. Make a call 37 | ```shell 38 | $ curl --location --request POST 'http://localhost:4566/restapis//compose/_user_request_/somePathId' \ 39 | --header 'Content-Type: application/json' \ 40 | --data-raw '{ 41 | "body": "{ \"name\": \"CoffeeBeans\" }" 42 | }' 43 | ``` 44 | The service responds 45 | ```json 46 | [ 47 | { 48 | "name": "CoffeeBeans", 49 | "saved": true 50 | } 51 | ] 52 | ``` 53 | 54 | #### Using `mvnw` 55 | 56 | 1. Run the following commands 57 | ```shell 58 | $ export SPRING_PROFILES_ACTIVE=local 59 | $ ./mvnw -ntp clean -Pnative -DskipTests native:compile package -pl spring-native-aws-lambda-function 60 | $ ./spring-native-aws-lambda-function/target/spring-native-aws-lambda-function 61 | ``` 62 | The service starts in less than 100 ms 63 | ```shell 64 | 2022-12-07 02:56:51.706 INFO 42417 --- [ main] c.c.s.Application : Starting Application using Java 17.0.4 65 | 2022-12-07 02:56:51.706 INFO 42417 --- [ main] c.c.s.Application : No active profile set, falling back to 1 default profile: "default" 66 | 2022-12-07 02:56:51.723 INFO 42417 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 67 | 2022-12-07 02:56:51.724 INFO 42417 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 68 | 2022-12-07 02:56:51.724 INFO 42417 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.68] 69 | 2022-12-07 02:56:51.733 INFO 42417 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 70 | 2022-12-07 02:56:51.733 INFO 42417 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 27 ms 71 | 2022-12-07 02:56:51.761 INFO 42417 --- [ main] o.s.c.f.web.mvc.FunctionHandlerMapping : FunctionCatalog: org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry@7efd575 72 | 2022-12-07 02:56:51.763 INFO 42417 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 73 | 2022-12-07 02:56:51.763 INFO 42417 --- [ main] c.c.s.Application : Started Application in 0.084 seconds (JVM running for 0.087) 74 | ``` 75 | 2. Make a call 76 | ```shell 77 | $ curl --location --request POST 'http://localhost:8080' \ 78 | --header 'Content-Type: application/json' \ 79 | --data-raw '{ 80 | "body": "{ \"name\": \"CoffeeBeans\" }" 81 | }' 82 | ``` 83 | The service responds 84 | ```json 85 | [ 86 | { 87 | "name": "CoffeeBeans", 88 | "saved": true 89 | } 90 | ] 91 | ``` 92 | 93 | ### Github action 94 | 95 | #### Initial setup for Github action to work 96 | 97 | 1. Create 98 | an [Identity providers in AWS for github actions](https://www.eliasbrange.dev/posts/secure-aws-deploys-from-github-actions-with-oidc/#:~:text=Add%20GitHub%20as%20an%20identity%20provider&text=To%20do%20that%2C%20navigate%20to,Provider%20type%2C%20select%20OpenID%20Connect). 99 | 2. Create a new `CoffeebeansCoreGithubActions` Iam role with the following inline IAM policy 100 | 101 | ```json 102 | { 103 | "Version": "2012-10-17", 104 | "Statement": [ 105 | { 106 | "Action": "iam:PassRole", 107 | "Resource": [ 108 | "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-lookup-role-{aws-account-number}-{aws-region}", 109 | "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-file-publishing-role-{aws-account-number}-{aws-region}", 110 | "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-image-publishing-role-{aws-account-number}-{aws-region}", 111 | "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-cfn-exec-role-{aws-account-number}-{aws-region}", 112 | "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-deploy-role-{aws-account-number}-{aws-region}" 113 | ], 114 | "Effect": "Allow" 115 | } 116 | ] 117 | } 118 | ``` 119 | 120 | and the following trust relationship 121 | 122 | ```json 123 | { 124 | "Version": "2012-10-17", 125 | "Statement": [ 126 | { 127 | "Effect": "Allow", 128 | "Principal": { 129 | "Federated": "arn:aws:iam::{aws-account-number}:oidc-provider/token.actions.githubusercontent.com" 130 | }, 131 | "Action": "sts:AssumeRoleWithWebIdentity", 132 | "Condition": { 133 | "StringEquals": { 134 | "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" 135 | }, 136 | "StringLike": { 137 | "token.actions.githubusercontent.com:sub": "repo:{github-account-or-org}/spring-native-aws-lambda:*" 138 | } 139 | } 140 | } 141 | ] 142 | } 143 | ``` 144 | 145 | 3. Create a new `CDKBootstrapForCoffeebeansCore` IAM role for CDK bootstrap with the following IAM 146 | managed policy `CoffeebeansCoreCdkBootstrapAccess` 147 | 148 | ```json 149 | { 150 | "Version": "2012-10-17", 151 | "Statement": [ 152 | { 153 | "Sid": "ECRPermissions", 154 | "Effect": "Allow", 155 | "Action": [ 156 | "ecr:CreateRepository", 157 | "ecr:DeleteRepository", 158 | "ecr:SetRepositoryPolicy", 159 | "ecr:DescribeRepositories" 160 | ], 161 | "Resource": "arn:aws:ecr:{aws-region}:{aws-account-number}:repository/cdk-{qualifier}-container-assets-{aws-account-number}-{aws-region}" 162 | }, 163 | { 164 | "Sid": "IAMPermissions", 165 | "Effect": "Allow", 166 | "Action": [ 167 | "iam:GetRole", 168 | "iam:CreateRole", 169 | "iam:DeleteRole", 170 | "iam:AttachRolePolicy", 171 | "iam:PutRolePolicy", 172 | "iam:DetachRolePolicy", 173 | "iam:DeleteRolePolicy" 174 | ], 175 | "Resource": [ 176 | "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-lookup-role-{aws-account-number}-{aws-region}", 177 | "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-file-publishing-role-{aws-account-number}-{aws-region}", 178 | "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-image-publishing-role-{aws-account-number}-{aws-region}", 179 | "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-cfn-exec-role-{aws-account-number}-{aws-region}", 180 | "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-deploy-role-{aws-account-number}-{aws-region}" 181 | ] 182 | }, 183 | { 184 | "Sid": "S3Permissions", 185 | "Effect": "Allow", 186 | "Action": [ 187 | "s3:PutBucketPublicAccessBlock", 188 | "s3:CreateBucket", 189 | "s3:DeleteBucketPolicy", 190 | "s3:PutEncryptionConfiguration", 191 | "s3:GetEncryptionConfiguration", 192 | "s3:PutBucketPolicy", 193 | "s3:DeleteBucket", 194 | "s3:PutBucketVersioning" 195 | ], 196 | "Resource": [ 197 | "arn:aws:s3:::{qualifier}-cdk-bucket" 198 | ] 199 | }, 200 | { 201 | "Sid": "SSMPermissions", 202 | "Effect": "Allow", 203 | "Action": [ 204 | "ssm:DeleteParameter", 205 | "ssm:AddTagsToResource", 206 | "ssm:GetParameters", 207 | "ssm:PutParameter" 208 | ], 209 | "Resource": "arn:aws:ssm:{aws-region}:{aws-account-number}:parameter/cdk-bootstrap/{qualifier}/version" 210 | } 211 | ] 212 | } 213 | ``` 214 | 215 | 4. Create an IAM managed policy `CoffeebeansCoreCdkExecutionAccess` to be used 216 | by `cdk-{qualifier}-cfn-exec-role-{aws-account-number}-{aws-region}` which is gonna be created by 217 | CDK 218 | 219 | ```json 220 | { 221 | "Version": "2012-10-17", 222 | "Statement": [ 223 | { 224 | "Sid": "S3Permissions", 225 | "Effect": "Allow", 226 | "Action": "s3:GetObject", 227 | "Resource": [ 228 | "arn:aws:s3:::{qualifier}-cdk-bucket", 229 | "arn:aws:s3:::{qualifier}-cdk-bucket/*" 230 | ] 231 | }, 232 | { 233 | "Sid": "AGWPermissions", 234 | "Effect": "Allow", 235 | "Action": [ 236 | "apigateway:POST", 237 | "apigateway:DELETE", 238 | "apigateway:GET", 239 | "apigateway:PATCH", 240 | "apigateway:PUT" 241 | ], 242 | "Resource": [ 243 | "arn:aws:apigateway:{aws-region}::/restapis", 244 | "arn:aws:apigateway:{aws-region}::/restapis/*", 245 | "arn:aws:apigateway:{aws-region}::/account" 246 | ] 247 | }, 248 | { 249 | "Sid": "SNSPermissions", 250 | "Effect": "Allow", 251 | "Action": [ 252 | "SNS:CreateTopic", 253 | "SNS:DeleteTopic", 254 | "SNS:Subscribe", 255 | "SNS:GetTopicAttributes", 256 | "SNS:ListSubscriptionsByTopic", 257 | "SNS:Unsubscribe", 258 | "SNS:TagResource", 259 | "SNS:UntagResource" 260 | ], 261 | "Resource": [ 262 | "arn:aws:sns:{aws-region}:{aws-account-number}:spring-native-aws-lambda-function-dead-letter-topic" 263 | ] 264 | }, 265 | { 266 | "Sid": "LambdaPermissions", 267 | "Effect": "Allow", 268 | "Action": [ 269 | "lambda:GetFunction", 270 | "lambda:ListFunctions", 271 | "lambda:DeleteFunction", 272 | "lambda:CreateFunction", 273 | "lambda:TagResource", 274 | "lambda:AddPermission", 275 | "lambda:RemovePermission", 276 | "lambda:PutFunctionEventInvokeConfig", 277 | "lambda:DeleteFunctionEventInvokeConfig", 278 | "lambda:UpdateFunctionEventInvokeConfig", 279 | "lambda:UpdateFunctionCode", 280 | "lambda:ListTags", 281 | "lambda:UpdateFunctionConfiguration" 282 | ], 283 | "Resource": [ 284 | "arn:aws:lambda:{aws-region}:{aws-account-number}:function:spring-native-aws-lambda-function", 285 | "arn:aws:lambda:{aws-region}:{aws-account-number}:function:spring-native-aws-lambda-function:$LATEST" 286 | ] 287 | }, 288 | { 289 | "Sid": "SSMPermissions", 290 | "Effect": "Allow", 291 | "Action": [ 292 | "ssm:GetParameters" 293 | ], 294 | "Resource": [ 295 | "arn:aws:ssm:{aws-region}:{aws-account-number}:parameter/cdk-bootstrap/{qualifier}/version" 296 | ] 297 | }, 298 | { 299 | "Sid": "IAMPermissions", 300 | "Effect": "Allow", 301 | "Action": [ 302 | "iam:PassRole", 303 | "iam:GetRole", 304 | "iam:GetRolePolicy", 305 | "iam:CreateRole", 306 | "iam:PutRolePolicy", 307 | "iam:DeleteRole", 308 | "iam:DeleteRolePolicy", 309 | "iam:AttachRolePolicy", 310 | "iam:DetachRolePolicy" 311 | ], 312 | "Resource": [ 313 | "arn:aws:iam::{aws-account-number}:role/spring-native-aws-lambda-springnativeawslambdafun-*", 314 | "arn:aws:iam::{aws-account-number}:role/spring-native-aws-lambda-springnativeawslambdares-4FVJBBHF9EL2", 315 | "arn:aws:iam::{aws-account-number}:role/spring-native-aws-lambda-function-rest-api/CloudWatchRole" 316 | ] 317 | }, 318 | { 319 | "Sid": "CFNPermissions", 320 | "Effect": "Allow", 321 | "Action": "cloudformation:DescribeStacks", 322 | "Resource": "arn:aws:cloudformation:{aws-region}:{aws-account-number}:stack/{qualifier}-example-function-dev-stack/*" 323 | } 324 | ] 325 | } 326 | ``` 327 | 328 | 5. Run the following command to bootstrap the CDK 329 | 330 | ```bash 331 | cdk bootstrap aws://{aws-account-number}/{aws-region} --profile cdk \ 332 | --role-arn arn:aws:iam::{aws-account-number}:role/CDKBootstrapForCoffeebeansCore \ 333 | --cloudformation-execution-policies "arn:aws:iam::{aws-account-number}:policy/CoffeebeansCoreCdkExecutionAccess" \ 334 | --toolkit-stack-name cdk-{qualifier}-toolkit \ 335 | --toolkit-bucket-name {qualifier}-cdk-bucket \ 336 | --qualifier {qualifier} \ 337 | --tags COST_CENTRE=coffeebeans-core 338 | ``` 339 | 340 | **NOTE**: notice that the policy passed to `--cloudformation-execution-policies` option is the one 341 | we created 342 | in step 4 343 | 344 | #### Building AWS Lambda Function from Zip 345 | 346 | Now that the setup is done you can deploy to AWS. 347 | 348 | 1. Create a new release in 349 | Github [releases page](https://github.com/muhamadto/spring-native-aws-lambda/releases), 350 | the [github action](.github/workflows/release.yml) will start and a deployment to AWS 351 | environment. 352 | 2. Test via curl 353 | ```shell 354 | $ curl --location --request POST 'https://{api-id}.execute-api.ap-southeast-2.amazonaws.com/dev/name' \ 355 | --header 'Content-Type: application/json' \ 356 | --data-raw '{ 357 | "name": "CoffeeBeans" 358 | }' 359 | ``` 360 | 3. Et voila! It runs with 500 ms for cold start. -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "./mvnw exec:java -pl spring-native-aws-lambda-infra -Dexec.mainClass=com.coffeebeans.springnativeawslambda.infra.Application" 3 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Licensed to Muhammad Hamadto 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # See the NOTICE file distributed with this work for additional information regarding copyright ownership. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | version: "3.9" 17 | services: 18 | spring-native-aws-lambda-function-infra: 19 | image: muhamadto/spring-native-amazonlinux2-builder:21-amazonlinux2-awscliv2 20 | volumes: 21 | - ./:/app 22 | - ${M2_REPO}:/home/worker/.m2 23 | working_dir: /app 24 | user: worker 25 | environment: 26 | AWS_DEFAULT_REGION: ap-southeast-2 27 | AWS_REGION: ap-southeast-2 28 | AWS_ACCESS_KEY_ID: local 29 | AWS_SECRET_ACCESS_KEY: local 30 | AWS_ENDPOINT_URL: http://localstack:4566 31 | BUILD_ARTIFACT: 'true' 32 | FUNCTION_NAME: spring-native-aws-lambda-function 33 | STAGE: compose 34 | MAVEN_OPTS: | 35 | -DskipTests=true 36 | -Dcheckstyle.skip=true 37 | -Djacoco.skip=true 38 | -Dsonar.skip=true 39 | -Dskip.it=true 40 | -Dmaven.javadoc.skip=true 41 | -Dmaven.source.skip=true 42 | -Dspring-boot.run.profiles=local 43 | entrypoint: 44 | - bash 45 | - -c 46 | command: | 47 | ' 48 | function package_spring_native_lambda() { 49 | if [ "$$BUILD_ARTIFACT" = "true" ]; then 50 | ./mvnw -ntp clean -Pnative -DskipTests native:compile package -pl "$$FUNCTION_NAME" 51 | else 52 | print_info_message "plain" "BUILD_ARTIFACT environment variable is not set. Skipping Maven build." 53 | fi 54 | } 55 | 56 | aws --version && 57 | 58 | source /usr/local/bin/awscliv2-util/aws && 59 | 60 | print_info_message "block" "Creating '"$$FUNCTION_NAME"'" && 61 | 62 | print_info_message "divider" "Package GraalVM function" && 63 | 64 | package_spring_native_lambda && 65 | 66 | print_info_message "divider" "Creating LAMBDA function" && 67 | lambda_create_function "$$FUNCTION_NAME" provided.al2023 512 ./"$$FUNCTION_NAME"/target/"$$FUNCTION_NAME"-native-zip.zip "$$FUNCTION_NAME" && 68 | lambda_wait_for_function "$$FUNCTION_NAME" && 69 | lambda_list_functions && 70 | LAMBDA_ARN="$(lambda_get_function_arn "$$FUNCTION_NAME")" && 71 | 72 | print_info_message "divider" "Creating API Gateway" && 73 | REST_API_ID="$(apigateway_create_restApi "somerestapiname")" && 74 | RESOURCE_ID="$(apigateway_create_resource "$$REST_API_ID" "somePathId")" 75 | apigateway_create_method "$$REST_API_ID" "$$RESOURCE_ID" "POST" && 76 | apigateway_create_lambda_integration "$$REST_API_ID" "$$RESOURCE_ID" "POST" "$$LAMBDA_ARN" "ap-southeast-2" && 77 | apigateway_create_deployment "$$REST_API_ID" "$$STAGE" && 78 | apigateway_list_restapis && 79 | 80 | print_info_message "plain" "Endpoint available at: http://localhost:4566/restapis/$$REST_API_ID/$$STAGE/_user_request_/somePathId" && 81 | 82 | print_info_message "block" "Successfully creating '"$$FUNCTION_NAME"'" 83 | ' 84 | depends_on: 85 | - localstack 86 | 87 | localstack: 88 | container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}" 89 | image: localstack/localstack 90 | ports: 91 | - "127.0.0.1:4566:4566" # LocalStack Gateway 92 | - "127.0.0.1:4510-4559:4510-4559" # external services port range 93 | environment: 94 | # LocalStack configuration: https://docs.localstack.cloud/references/configuration/ 95 | - DEBUG=${DEBUG:-0} 96 | volumes: 97 | - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack" 98 | - "/var/run/docker.sock:/var/run/docker.sock" 99 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | lombok.addLombokGeneratedAnnotation=true 2 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 23 | 4.0.0 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-parent 28 | 3.2.1 29 | 30 | 31 | 32 | com.coffeebeans 33 | spring-native-aws-lambda 34 | ${project.artifactId} 35 | ${revision} 36 | pom 37 | Demo project for Spring cloud function with graalvm native image deployed with cdk 38 | 39 | 40 | 41 | 42 | matto 43 | Muhammad Hamadto 44 | https://www.linkedin.com/in/muhamadto/ 45 | 46 | 47 | 48 | 49 | 1.0.0-SNAPSHOT 50 | 51 | UTF-8 52 | UTF-8 53 | 54 | 21 55 | true 56 | 57 | muhamadto 58 | https://sonarcloud.io 59 | 60 | e1,e2 61 | java:S107 62 | 63 | **/cdk/*.java 64 | 65 | 66 | 68 | java:S110 69 | 70 | **/cdk/*.java 71 | 72 | 73 | ${project.basedir}/target/site/jacoco/jacoco.xml, 74 | ${project.basedir}/target/site/jacoco-it/jacoco.xml 75 | 76 | 77 | **/model/*.java, **/Application.java, **/*Hints.java 78 | 79 | 80 | 81 | 82 | spring-native-aws-lambda-function 83 | spring-native-aws-lambda-infra 84 | 85 | 86 | 87 | 88 | 89 | 90 | com.coffeebeans 91 | spring-native-aws-lambda-function 92 | ${project.version} 93 | 94 | 95 | com.coffeebeans 96 | spring-native-aws-lambda-infra 97 | ${project.version} 98 | 99 | 100 | 101 | 102 | 103 | 104 | com.amazonaws 105 | aws-lambda-java-events 106 | 3.11.4 107 | 108 | 109 | com.amazonaws 110 | aws-lambda-java-core 111 | 1.2.3 112 | 113 | 114 | com.amazonaws 115 | aws-lambda-java-serialization 116 | 1.1.5 117 | 118 | 119 | com.amazonaws 120 | aws-lambda-java-log4j2 121 | 1.6.0 122 | 123 | 124 | software.amazon.lambda 125 | powertools-logging 126 | 1.18.0 127 | 128 | 129 | 130 | 131 | 132 | org.apache.commons 133 | commons-lang3 134 | 3.14.0 135 | 136 | 137 | 138 | org.apache.commons 139 | commons-collections4 140 | 4.4 141 | 142 | 143 | 144 | javax.validation 145 | validation-api 146 | 2.0.1.Final 147 | 148 | 149 | 150 | com.google.guava 151 | guava 152 | 33.0.0-jre 153 | 154 | 155 | 156 | org.projectlombok 157 | lombok 158 | ${lombok.version} 159 | 160 | 161 | 162 | 163 | 164 | 165 | com.fasterxml.jackson.core 166 | jackson-databind 167 | 168 | 169 | 170 | com.fasterxml.jackson.module 171 | jackson-module-parameter-names 172 | ${jackson-bom.version} 173 | 174 | 175 | 176 | com.fasterxml.jackson.datatype 177 | jackson-datatype-jsr310 178 | 179 | 180 | 181 | com.fasterxml.jackson.datatype 182 | jackson-datatype-jdk8 183 | 184 | 185 | 186 | 187 | 188 | software.amazon.awscdk 189 | aws-cdk-lib 190 | 2.116.1 191 | 192 | 193 | 194 | 195 | 196 | cloud.pianola 197 | cdk-fluent-assertions 198 | 1.0.1 199 | test 200 | 201 | 202 | 203 | org.assertj 204 | assertj-core 205 | ${assertj.version} 206 | test 207 | 208 | 209 | 210 | org.junit.jupiter 211 | junit-jupiter-api 212 | ${junit-jupiter.version} 213 | test 214 | 215 | 216 | 217 | org.mockito 218 | mockito-core 219 | ${mockito.version} 220 | test 221 | 222 | 223 | 224 | 225 | 226 | 227 | ${project.artifactId} 228 | 229 | 230 | 231 | org.apache.maven.plugins 232 | maven-surefire-plugin 233 | 3.2.3 234 | 235 | 236 | org.apache.maven.plugins 237 | maven-compiler-plugin 238 | 3.12.0 239 | 240 | 241 | org.jacoco 242 | jacoco-maven-plugin 243 | 0.8.11 244 | 245 | 246 | pre-unit-test 247 | 248 | prepare-agent 249 | 250 | 251 | 252 | post-unit-test 253 | 254 | report 255 | 256 | 257 | 258 | pre-integration-test 259 | 260 | prepare-agent-integration 261 | 262 | 263 | 264 | post-integration-test 265 | 266 | report-integration 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | Apache License, Version 2.0 277 | https://www.apache.org/licenses/LICENSE-2.0 278 | 279 | Licensed to Muhammad Hamadto 280 | 281 | Licensed under the Apache License, Version 2.0 (the "License"); 282 | you may not use this file except in compliance with the License. 283 | You may obtain a copy of the License at 284 | https://www.apache.org/licenses/LICENSE-2.0 285 | 286 | See the NOTICE file distributed with this work for additional information regarding 287 | copyright ownership. 288 | 289 | Unless required by applicable law or agreed to in writing, software 290 | distributed under the License is distributed on an "AS IS" BASIS, 291 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 292 | See the License for the specific language governing permissions and 293 | limitations under the License. 294 | 295 | 296 | 297 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 23 | 4.0.0 24 | 25 | 26 | com.coffeebeans 27 | spring-native-aws-lambda 28 | ${revision} 29 | 30 | 31 | spring-native-aws-lambda-function 32 | ${project.artifactId} 33 | jar 34 | Demo project for Spring cloud function with graalvm native image 35 | 36 | 37 | 38 | matto 39 | Muhammad Hamadto 40 | https://www.linkedin.com/in/muhamadto/ 41 | 42 | 43 | 44 | 45 | 2023.0.0 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.cloud 53 | spring-cloud-dependencies 54 | ${spring-cloud.version} 55 | pom 56 | import 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | com.amazonaws 66 | aws-lambda-java-events 67 | 68 | 69 | com.amazonaws 70 | aws-lambda-java-core 71 | 72 | 73 | com.amazonaws 74 | aws-lambda-java-serialization 75 | 76 | 77 | com.amazonaws 78 | aws-lambda-java-log4j2 79 | 80 | 81 | 82 | 83 | 84 | org.springframework.cloud 85 | spring-cloud-function-adapter-aws 86 | 87 | 88 | 89 | org.springframework.cloud 90 | spring-cloud-starter-function-web 91 | 92 | 93 | 94 | 95 | 96 | org.springframework.boot 97 | spring-boot-starter-web 98 | 99 | 100 | org.springframework.boot 101 | spring-boot-starter-validation 102 | 103 | 104 | 105 | 106 | 107 | org.projectlombok 108 | lombok 109 | 110 | 111 | 112 | 113 | 114 | org.springframework.boot 115 | spring-boot-starter-test 116 | test 117 | 118 | 119 | org.junit.vintage 120 | junit-vintage-engine 121 | 122 | 123 | 124 | 125 | org.assertj 126 | assertj-core 127 | test 128 | 129 | 130 | 131 | 132 | 133 | 134 | ${project.artifactId} 135 | 136 | 137 | 138 | 139 | native 140 | 141 | 142 | 143 | org.springframework.boot 144 | spring-boot-maven-plugin 145 | 146 | 147 | spring-boot-build-info 148 | 149 | build-info 150 | 151 | generate-resources 152 | 153 | 154 | process-aot 155 | 156 | process-aot 157 | 158 | 159 | 160 | 161 | 162 | org.graalvm.buildtools 163 | native-maven-plugin 164 | 165 | com.coffeebeans.springnativeawslambda.Application 166 | 167 | --verbose 168 | --no-fallback 169 | --enable-preview 170 | --strict-image-heap 171 | -H:+ReportExceptionStackTraces 172 | 173 | 174 | 175 | 176 | maven-assembly-plugin 177 | 178 | 179 | native-zip 180 | package 181 | 182 | single 183 | 184 | false 185 | 186 | 187 | 188 | 189 | src/assembly/native.xml 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/assembly/native.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 22 | native-zip 23 | 24 | zip 25 | 26 | 27 | 28 | 29 | src/shell/native 30 | / 31 | true 32 | 0775 33 | 34 | bootstrap 35 | 36 | 37 | 38 | target 39 | / 40 | true 41 | 0775 42 | 43 | spring-native-aws-lambda-function 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/Application.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda; 20 | 21 | import org.springframework.boot.SpringApplication; 22 | import org.springframework.boot.autoconfigure.SpringBootApplication; 23 | import org.springframework.context.annotation.ImportRuntimeHints; 24 | 25 | @SpringBootApplication 26 | @ImportRuntimeHints({ 27 | ReflectionRuntimeHints.class, 28 | ResourcesRuntimeHints.class 29 | }) 30 | public class Application { 31 | 32 | public static void main(String[] args) { 33 | SpringApplication.run(Application.class, args); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/LambdaExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.coffeebeans.springnativeawslambda; 2 | 3 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; 4 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; 7 | import java.util.Map; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.web.bind.annotation.ExceptionHandler; 10 | import org.springframework.web.bind.annotation.RestControllerAdvice; 11 | 12 | @Slf4j 13 | @RestControllerAdvice 14 | public class LambdaExceptionHandler { 15 | 16 | @ExceptionHandler(ClassCastException.class) 17 | public APIGatewayProxyResponseEvent handleException( 18 | final ClassCastException e, 19 | final APIGatewayProxyRequestEvent request) { 20 | log.error("Error processing request: {}", e.getMessage()); 21 | return new APIGatewayProxyResponseEvent() 22 | .withStatusCode(500) 23 | .withHeaders(Map.of("X-Requested-Id", request.getRequestContext().getRequestId())) 24 | .withBody("{\n" 25 | + " \"message\": \"Internal Server Error\",\n" 26 | + " \"errorCode\": \"SERVER_ERROR\n" 27 | + " }"); 28 | } 29 | 30 | @ExceptionHandler(InvalidDefinitionException.class) 31 | public APIGatewayProxyResponseEvent handleException( 32 | final InvalidDefinitionException e, 33 | final APIGatewayProxyRequestEvent request) { 34 | log.error("Error processing request: {}", e.getMessage()); 35 | return new APIGatewayProxyResponseEvent() 36 | .withStatusCode(400) 37 | .withHeaders(Map.of("X-Requested-Id", request.getRequestContext().getRequestId())) 38 | .withBody("{\n" 39 | + " \"message\": \"Request body is Invalid\",\n" 40 | + " \"errorCode\": \"INVALID_INPUT\n" 41 | + " }"); 42 | } 43 | 44 | @ExceptionHandler(JsonProcessingException.class) 45 | public APIGatewayProxyResponseEvent handleException( 46 | final JsonProcessingException e, 47 | final APIGatewayProxyRequestEvent request) { 48 | log.error("Error processing request: {}", e.getMessage()); 49 | return new APIGatewayProxyResponseEvent() 50 | .withStatusCode(500) 51 | .withHeaders(Map.of("X-Requested-Id", request.getRequestContext().getRequestId())) 52 | .withBody("{\n" 53 | + " \"message\": \"Internal Server Error\",\n" 54 | + " \"errorCode\": \"SERVER_ERROR\n" 55 | + " }"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/ReflectionRuntimeHints.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda; 20 | 21 | import java.util.List; 22 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; 23 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; 24 | import com.coffeebeans.springnativeawslambda.model.Request; 25 | import com.coffeebeans.springnativeawslambda.model.Response; 26 | import org.joda.time.DateTime; 27 | import org.springframework.aot.hint.MemberCategory; 28 | import org.springframework.aot.hint.RuntimeHints; 29 | import org.springframework.aot.hint.RuntimeHintsRegistrar; 30 | import org.springframework.aot.hint.TypeReference; 31 | import org.springframework.lang.Nullable; 32 | 33 | public class ReflectionRuntimeHints implements RuntimeHintsRegistrar { 34 | 35 | @Override 36 | public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { 37 | final List typeReferences = List.of( 38 | TypeReference.of(DateTime.class), 39 | TypeReference.of(Response.class), 40 | TypeReference.of(Request.class), 41 | TypeReference.of(APIGatewayProxyResponseEvent.class), 42 | TypeReference.of(APIGatewayProxyRequestEvent.class), 43 | TypeReference.of(APIGatewayProxyRequestEvent.ProxyRequestContext.class), 44 | TypeReference.of(APIGatewayProxyRequestEvent.RequestIdentity.class)); 45 | 46 | hints.reflection().registerTypes(typeReferences, builder -> builder.withMembers(MemberCategory.values())); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/ResourcesRuntimeHints.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda; 20 | 21 | import org.springframework.aot.hint.RuntimeHints; 22 | import org.springframework.aot.hint.RuntimeHintsRegistrar; 23 | 24 | public class ResourcesRuntimeHints implements RuntimeHintsRegistrar { 25 | 26 | @Override 27 | public void registerHints(final RuntimeHints hints, final ClassLoader classLoader) { 28 | hints.resources().registerPattern("com/amazonaws/lambda/thirdparty/org/joda/time/tz/*"); 29 | } 30 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/function/ExampleFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.function; 20 | 21 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; 22 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; 23 | import com.coffeebeans.springnativeawslambda.model.Request; 24 | import com.coffeebeans.springnativeawslambda.model.Response; 25 | import com.fasterxml.jackson.core.JsonProcessingException; 26 | import com.fasterxml.jackson.databind.ObjectMapper; 27 | import jakarta.validation.constraints.NotNull; 28 | import java.util.function.Function; 29 | import lombok.SneakyThrows; 30 | import lombok.extern.slf4j.Slf4j; 31 | import org.springframework.stereotype.Component; 32 | import org.springframework.validation.annotation.Validated; 33 | 34 | @Component 35 | @Slf4j 36 | @Validated 37 | public class ExampleFunction implements 38 | Function { 39 | 40 | private final ObjectMapper objectMapper; 41 | 42 | public ExampleFunction(@NotNull final ObjectMapper objectMapper) { 43 | this.objectMapper = objectMapper; 44 | } 45 | 46 | /** 47 | * Lambda function handler that takes a request and returns a response. 48 | * 49 | * @param proxyRequestEvent the function argument 50 | * @return {@link APIGatewayProxyResponseEvent} 51 | * @throws JsonProcessingException 52 | */ 53 | @Override 54 | @SneakyThrows(value = JsonProcessingException.class) 55 | public APIGatewayProxyResponseEvent apply(final APIGatewayProxyRequestEvent proxyRequestEvent) { 56 | log.info("Converting request into a response...'"); 57 | 58 | final Request request = objectMapper.readValue(proxyRequestEvent.getBody(), Request.class); 59 | 60 | final Response response = Response.builder() 61 | .name(request.getName()) 62 | .saved(true) 63 | .build(); 64 | 65 | log.info("Converted request into a response."); 66 | 67 | return new APIGatewayProxyResponseEvent() 68 | .withStatusCode(200) 69 | .withBody(objectMapper.writeValueAsString(response)); 70 | } 71 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/model/Request.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.model; 20 | 21 | import jakarta.validation.constraints.NotBlank; 22 | import lombok.AllArgsConstructor; 23 | import lombok.Builder; 24 | import lombok.Data; 25 | import lombok.NoArgsConstructor; 26 | 27 | @Data 28 | @Builder 29 | @NoArgsConstructor 30 | @AllArgsConstructor 31 | public class Request { 32 | 33 | @NotBlank 34 | private String name; 35 | } 36 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/model/Response.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.model; 20 | 21 | import com.fasterxml.jackson.annotation.JsonProperty; 22 | import com.fasterxml.jackson.annotation.JsonProperty.Access; 23 | import jakarta.validation.constraints.NotBlank; 24 | import jakarta.validation.constraints.NotNull; 25 | import lombok.AllArgsConstructor; 26 | import lombok.Builder; 27 | import lombok.Data; 28 | import lombok.NoArgsConstructor; 29 | 30 | @Data 31 | @Builder 32 | @NoArgsConstructor 33 | @AllArgsConstructor 34 | public class Response { 35 | 36 | @NotBlank 37 | @JsonProperty(access = Access.READ_ONLY) 38 | private String name; 39 | 40 | @NotNull 41 | @JsonProperty(access = Access.READ_ONLY) 42 | private boolean saved; 43 | } 44 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/main/resources/application-local.yml: -------------------------------------------------------------------------------- 1 | # Licensed to Muhammad Hamadto 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # See the NOTICE file distributed with this work for additional information regarding copyright ownership. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | spring: 17 | main: 18 | web-application-type: servlet -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # Licensed to Muhammad Hamadto 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # See the NOTICE file distributed with this work for additional information regarding copyright ownership. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | spring: 17 | main: 18 | banner-mode: off 19 | web-application-type: none 20 | jackson: 21 | default-property-inclusion: NON_EMPTY 22 | deserialization: 23 | fail-on-unknown-properties: false 24 | serialization: 25 | write-dates-as-timestamps: false 26 | mapper: 27 | accept-case-insensitive-properties: true 28 | cloud: 29 | function: 30 | web: 31 | export: 32 | enabled: false 33 | debug: false 34 | definition: exampleFunction 35 | debug: false -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/shell/native/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Licensed to Muhammad Hamadto 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # See the NOTICE file distributed with this work for additional information regarding copyright ownership. 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # 19 | 20 | set -euo pipefail 21 | 22 | cd ${LAMBDA_TASK_ROOT:-.} 23 | 24 | ./spring-native-aws-lambda-function -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/test/java/com/coffeebeans/springnativeawslambda/ApplicationIT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | 23 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; 24 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; 25 | import com.fasterxml.jackson.core.JsonProcessingException; 26 | import com.fasterxml.jackson.databind.ObjectMapper; 27 | import org.junit.jupiter.api.Test; 28 | import org.springframework.beans.factory.annotation.Autowired; 29 | import org.springframework.boot.test.context.SpringBootTest; 30 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 31 | import org.springframework.cloud.function.adapter.test.aws.AWSCustomRuntime; 32 | import org.springframework.test.context.ContextConfiguration; 33 | import org.springframework.test.context.TestPropertySource; 34 | 35 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"spring.main.web-application-type=servlet"}) 36 | @ContextConfiguration(classes = {AWSCustomRuntime.class, Application.class}) 37 | @TestPropertySource(properties = {"_HANDLER=exampleFunction" 38 | }) 39 | class ApplicationIT { 40 | 41 | @Autowired 42 | private AWSCustomRuntime aws; 43 | 44 | @Autowired 45 | private ObjectMapper objectMapper; 46 | 47 | @Test 48 | void should_return_200() throws JsonProcessingException { 49 | 50 | final APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent() 51 | .withBody("{\"name\":\"Coffeebeans\"}"); 52 | 53 | final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() 54 | .withStatusCode(200) 55 | .withBody("{\"name\":\"Coffeebeans\",\"saved\":true}"); 56 | 57 | assertThat(aws.exchange(objectMapper.writeValueAsString(request)).getPayload()) 58 | .isEqualTo(objectMapper.writeValueAsString(response)); 59 | 60 | } 61 | 62 | @Test 63 | void should_return_400() throws JsonProcessingException { 64 | 65 | final APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent() 66 | .withBody("\"name\":\"Coffeebeans\"}"); 67 | 68 | final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() 69 | .withStatusCode(400) 70 | .withBody("{\"message\": \"Request body is Invalid\",\"errorCode\": \"INVALID_INPUT\"}"); 71 | 72 | assertThat(aws.exchange(objectMapper.writeValueAsString(request)).getPayload()) 73 | .isEqualTo(objectMapper.writeValueAsString(response)); 74 | 75 | } 76 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-function/src/test/java/com/coffeebeans/springnativeawslambda/function/ExampleFunctionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.function; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 23 | 24 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; 25 | import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; 26 | import com.coffeebeans.springnativeawslambda.function.ExampleFunction; 27 | import com.fasterxml.jackson.core.JsonProcessingException; 28 | import com.fasterxml.jackson.databind.ObjectMapper; 29 | import org.junit.jupiter.api.BeforeEach; 30 | import org.junit.jupiter.api.Test; 31 | import org.mockito.Spy; 32 | 33 | class ExampleFunctionTest { 34 | 35 | private ExampleFunction exampleFunction; 36 | 37 | @Spy 38 | private ObjectMapper objectMapper = new ObjectMapper(); 39 | 40 | @BeforeEach 41 | void setUp() { 42 | exampleFunction = new ExampleFunction(objectMapper); 43 | } 44 | 45 | @Test 46 | void should_return_APIGatewayProxyResponseEvent() { 47 | final String requestBody = "{\"name\":\"Coffeebeans\"}"; 48 | final String responseBody = "{\"name\":\"Coffeebeans\",\"saved\":true}"; 49 | 50 | final APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent() 51 | .withBody(requestBody); 52 | 53 | final APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent = new APIGatewayProxyResponseEvent() 54 | .withStatusCode(200) 55 | .withBody(responseBody); 56 | 57 | final APIGatewayProxyResponseEvent actual = exampleFunction.apply(request); 58 | 59 | assertThat(actual) 60 | .isEqualTo(apiGatewayProxyResponseEvent); 61 | } 62 | 63 | @Test 64 | void should_throw_JsonProcessingException() { 65 | 66 | final APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent() 67 | .withBody("Coffeebeans"); 68 | 69 | assertThatThrownBy(() -> exampleFunction.apply(request)) 70 | .isInstanceOf(JsonProcessingException.class); 71 | } 72 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 23 | 4.0.0 24 | 25 | 26 | com.coffeebeans 27 | spring-native-aws-lambda 28 | ${revision} 29 | 30 | 31 | spring-native-aws-lambda-infra 32 | ${project.artifactId} 33 | jar 34 | Infrastructure as Code for deploying a Spring cloud function demo project with 35 | graalvm native image 36 | 37 | 38 | 39 | 40 | matto 41 | Muhammad Hamadto 42 | https://www.linkedin.com/in/muhamadto/ 43 | 44 | 45 | 46 | 47 | 21 48 | 49 | 50 | 51 | 52 | 53 | software.amazon.awscdk 54 | aws-cdk-lib 55 | 56 | 57 | 58 | 59 | 60 | org.projectlombok 61 | lombok 62 | compile 63 | 64 | 65 | 66 | org.apache.commons 67 | commons-lang3 68 | 69 | 70 | 71 | org.apache.commons 72 | commons-collections4 73 | 74 | 75 | 76 | javax.validation 77 | validation-api 78 | 79 | 80 | 81 | com.google.guava 82 | guava 83 | 84 | 85 | 86 | 87 | 88 | 89 | com.fasterxml.jackson.module 90 | jackson-module-parameter-names 91 | 92 | 93 | 94 | 95 | 96 | 97 | cloud.pianola 98 | cdk-fluent-assertions 99 | test 100 | 101 | 102 | 103 | org.assertj 104 | assertj-core 105 | test 106 | 107 | 108 | 109 | org.junit.jupiter 110 | junit-jupiter-api 111 | test 112 | 113 | 114 | 115 | org.mockito 116 | mockito-core 117 | test 118 | 119 | 120 | 121 | 122 | 123 | ${project.artifactId} 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-deploy-plugin 128 | 3.1.1 129 | 130 | true 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/ApiBaseStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import com.coffeebeans.springnativeawslambda.infra.lambda.CustomRuntime2023Function; 22 | import org.apache.commons.lang3.StringUtils; 23 | import software.amazon.awscdk.Duration; 24 | import software.amazon.awscdk.Stack; 25 | import software.amazon.awscdk.StackProps; 26 | import software.amazon.awscdk.services.apigateway.LambdaRestApi; 27 | import software.amazon.awscdk.services.apigateway.Resource; 28 | import software.amazon.awscdk.services.apigateway.StageOptions; 29 | import software.amazon.awscdk.services.ec2.IVpc; 30 | import software.amazon.awscdk.services.iam.IRole; 31 | import software.amazon.awscdk.services.iam.Role; 32 | import software.amazon.awscdk.services.lambda.Code; 33 | import software.amazon.awscdk.services.lambda.Function; 34 | import software.amazon.awscdk.services.lambda.FunctionProps; 35 | import software.amazon.awscdk.services.sns.ITopic; 36 | import software.amazon.awscdk.services.sns.Topic; 37 | import software.amazon.awscdk.services.sns.subscriptions.SqsSubscription; 38 | import software.amazon.awscdk.services.sqs.DeadLetterQueue; 39 | import software.amazon.awscdk.services.sqs.DeduplicationScope; 40 | import software.amazon.awscdk.services.sqs.Queue; 41 | import software.constructs.Construct; 42 | 43 | import javax.validation.constraints.NotBlank; 44 | import javax.validation.constraints.NotEmpty; 45 | import javax.validation.constraints.NotNull; 46 | import java.util.Map; 47 | 48 | import static software.amazon.awscdk.services.lambda.Runtime.PROVIDED_AL2023; 49 | 50 | public class ApiBaseStack extends Stack { 51 | 52 | private static final int LAMBDA_FUNCTION_TIMEOUT_IN_SECONDS = 3; 53 | private static final int LAMBDA_FUNCTION_MEMORY_SIZE = 512; 54 | private static final int LAMBDA_FUNCTION_RETRY_ATTEMPTS = 2; 55 | private static final String FIFO_SUFFIX = ".fifo"; 56 | private static final String DEAD_LETTER_QUEUE_SUFFIX = "-dlq"; 57 | 58 | public ApiBaseStack( 59 | @NotNull final Construct scope, 60 | @NotBlank final String id, 61 | @NotNull final StackProps props) { 62 | super(scope, id, props); 63 | } 64 | 65 | @NotNull 66 | protected Queue createQueue(@NotBlank final String queueId) { 67 | final DeadLetterQueue deadLetterQueue = createDeadLetterQueue( 68 | queueId + DEAD_LETTER_QUEUE_SUFFIX); 69 | 70 | return Queue.Builder.create(this, queueId) 71 | .queueName(queueId) 72 | .deadLetterQueue(deadLetterQueue) 73 | .build(); 74 | } 75 | 76 | @NotNull 77 | protected Queue createFifoQueue( 78 | @NotBlank final String queueId, 79 | final boolean contentBasedDeduplication, 80 | @NotNull final DeduplicationScope messageGroup) { 81 | final String fifoQueueId = queueId + FIFO_SUFFIX; 82 | final String fifoDeadLetterQueueId = queueId + DEAD_LETTER_QUEUE_SUFFIX; 83 | 84 | final DeadLetterQueue deadLetterQueue = 85 | createFifoDeadLetterQueue(fifoDeadLetterQueueId, contentBasedDeduplication, messageGroup); 86 | 87 | return Queue.Builder.create(this, fifoQueueId) 88 | .queueName(fifoQueueId) 89 | .fifo(true) 90 | .deadLetterQueue(deadLetterQueue) 91 | .contentBasedDeduplication(contentBasedDeduplication) 92 | .deduplicationScope(messageGroup) 93 | .build(); 94 | } 95 | 96 | @NotNull 97 | protected DeadLetterQueue createDeadLetterQueue(@NotBlank final String deadLetterQueueId) { 98 | final Queue queue = Queue.Builder.create(this, deadLetterQueueId) 99 | .queueName(deadLetterQueueId) 100 | .build(); 101 | 102 | return DeadLetterQueue.builder() 103 | .queue(queue) 104 | .maxReceiveCount(3) 105 | .build(); 106 | } 107 | 108 | @NotNull 109 | protected DeadLetterQueue createFifoDeadLetterQueue( 110 | @NotBlank final String deadLetterQueueId, 111 | final boolean contentBasedDeduplication, 112 | @NotNull final DeduplicationScope messageGroup) { 113 | final String fifoDeadLetterQueueId = deadLetterQueueId + FIFO_SUFFIX; 114 | final Queue queue = Queue.Builder.create(this, fifoDeadLetterQueueId) 115 | .queueName(fifoDeadLetterQueueId) 116 | .fifo(true) 117 | .contentBasedDeduplication(contentBasedDeduplication) 118 | .deduplicationScope(messageGroup) 119 | .build(); 120 | 121 | return DeadLetterQueue.builder() 122 | .queue(queue) 123 | .maxReceiveCount(3) 124 | .build(); 125 | } 126 | 127 | @NotNull 128 | protected Topic createTopic( 129 | @NotBlank final String topicId) { 130 | 131 | return Topic.Builder.create(this, topicId) 132 | .topicName(topicId) 133 | .build(); 134 | } 135 | 136 | @NotNull 137 | protected Topic createFifoTopic( 138 | @NotBlank final String topicId, 139 | final boolean fifo, 140 | final boolean contentBasedDeduplication) { 141 | String fifoTopicId = topicId + FIFO_SUFFIX; 142 | 143 | return Topic.Builder.create(this, fifoTopicId) 144 | .topicName(fifoTopicId) 145 | .fifo(fifo) 146 | .contentBasedDeduplication(contentBasedDeduplication) 147 | .build(); 148 | } 149 | 150 | @NotNull 151 | protected static SqsSubscription createSqsSubscription(@NotNull final Queue queue) { 152 | return SqsSubscription.Builder.create(queue).build(); 153 | } 154 | 155 | @NotNull 156 | protected Function createFunction( 157 | @NotBlank final String lambdaId, 158 | @NotBlank final String handler, 159 | @NotNull final Code code, 160 | @NotNull final Topic deadLetterTopic, 161 | @NotNull Role role, 162 | @NotEmpty final Map environment) { 163 | return this.createFunction(null, 164 | lambdaId, 165 | handler, 166 | code, 167 | deadLetterTopic, 168 | role, 169 | environment); 170 | } 171 | 172 | @NotNull 173 | protected Function createFunction( 174 | final IVpc vpc, 175 | @NotBlank final String lambdaId, 176 | @NotBlank final String handler, 177 | @NotNull final Code code, 178 | @NotNull final ITopic deadLetterTopic, 179 | @NotNull IRole role, 180 | @NotEmpty final Map environment) { 181 | 182 | FunctionProps functionProps = FunctionProps.builder() 183 | .runtime(PROVIDED_AL2023) 184 | .functionName(lambdaId) 185 | .description("Lambda example with spring native") 186 | .code(code) 187 | .handler(handler) 188 | .role(role) 189 | .vpc(vpc) 190 | .environment(environment) 191 | .deadLetterTopic(deadLetterTopic) 192 | .timeout(Duration.seconds(LAMBDA_FUNCTION_TIMEOUT_IN_SECONDS)) 193 | .memorySize(LAMBDA_FUNCTION_MEMORY_SIZE) 194 | .retryAttempts(LAMBDA_FUNCTION_RETRY_ATTEMPTS) 195 | .build(); 196 | 197 | return new CustomRuntime2023Function(this, lambdaId, functionProps) 198 | .getFunction(); 199 | 200 | } 201 | 202 | @NotNull 203 | protected LambdaRestApi createLambdaRestApi( 204 | @NotBlank final String stageName, 205 | @NotBlank final String restApiId, 206 | @NotNull final String resourceName, 207 | @NotNull final String httpMethod, 208 | @NotNull final Function function, 209 | final boolean proxy) { 210 | 211 | // point to the lambda 212 | final LambdaRestApi lambdaRestApi = LambdaRestApi.Builder.create(this, restApiId) 213 | .restApiName(restApiId) 214 | .handler(function) 215 | .proxy(proxy) 216 | .deployOptions(StageOptions.builder().stageName(stageName).build()) 217 | .build(); 218 | 219 | // get root resource to add methods 220 | final Resource resource = lambdaRestApi.getRoot().addResource(resourceName); 221 | resource.addMethod(StringUtils.toRootUpperCase(httpMethod)); 222 | 223 | return lambdaRestApi; 224 | } 225 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Application.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import lombok.NoArgsConstructor; 22 | import software.amazon.awscdk.App; 23 | import software.amazon.awscdk.Tags; 24 | 25 | import java.util.Map; 26 | import java.util.Objects; 27 | 28 | import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE; 29 | import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_ENV; 30 | import static com.coffeebeans.springnativeawslambda.infra.StackUtils.createStack; 31 | import static com.coffeebeans.springnativeawslambda.infra.TagUtils.createTags; 32 | import static com.google.common.base.Preconditions.checkNotNull; 33 | import static lombok.AccessLevel.PRIVATE; 34 | 35 | @NoArgsConstructor(access = PRIVATE) 36 | public final class Application { 37 | 38 | private static final String DEV_STACK_NAME = "spring-native-aws-lambda-function-dev-stack"; 39 | private static final String PRD_STACK_NAME = "spring-native-aws-lambda-function-prd-stack"; 40 | private static final String ENVIRONMENT_NAME_DEV = "dev"; 41 | private static final String ENVIRONMENT_NAME_PRD = "prd"; 42 | private static final String LAMBDA_CODE_PATH = 43 | SpringNativeAwsLambdaStack.LAMBDA_FUNCTION_ID 44 | + "/target/spring-native-aws-lambda-function-native-zip.zip"; 45 | private static final String QUALIFIER = "cbcore"; 46 | private static final String FILE_ASSETS_BUCKET_NAME = "cbcore-cdk-bucket"; 47 | 48 | public static void main(final String... args) { 49 | final App app = new App(); 50 | 51 | final String env = System.getenv(KEY_ENV); 52 | checkNotNull(env, "'env' environment variable is required"); 53 | final Map tags = createTags(env, KEY_COST_CENTRE); 54 | 55 | switch (env) { 56 | case ENVIRONMENT_NAME_DEV -> 57 | createStack(app, DEV_STACK_NAME, LAMBDA_CODE_PATH, QUALIFIER, FILE_ASSETS_BUCKET_NAME, env); 58 | case ENVIRONMENT_NAME_PRD -> 59 | createStack(app, PRD_STACK_NAME, LAMBDA_CODE_PATH, QUALIFIER, FILE_ASSETS_BUCKET_NAME, env); 60 | default -> throw new IllegalArgumentException("Environment variable " + KEY_ENV 61 | + " is not set to a valid value. Set it to '[dev|prd]'"); 62 | } 63 | 64 | tags.entrySet().stream() 65 | .filter(tag -> Objects.nonNull(tag.getValue())) 66 | .forEach(tag -> Tags.of(app).add(tag.getKey(), tag.getValue())); 67 | 68 | app.synth(); 69 | } 70 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/CoffeeBeansConstruct.java: -------------------------------------------------------------------------------- 1 | package com.coffeebeans.springnativeawslambda.infra; 2 | 3 | import software.constructs.IConstruct; 4 | 5 | public interface CoffeeBeansConstruct extends IConstruct { 6 | 7 | 8 | } 9 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Constants.java: -------------------------------------------------------------------------------- 1 | package com.coffeebeans.springnativeawslambda.infra; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 7 | public class Constants { 8 | public static final String KEY_ENV = "ENV"; 9 | public static final String KEY_COST_CENTRE = "COST_CENTRE"; 10 | public static final String KEY_APPLICATION_NAME = "applicationName"; 11 | 12 | public static final String VALUE_COST_CENTRE = "coffeeBeans-core"; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/SpringNativeAwsLambdaStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import software.amazon.awscdk.StackProps; 22 | import software.amazon.awscdk.services.iam.IManagedPolicy; 23 | import software.amazon.awscdk.services.iam.Role; 24 | import software.amazon.awscdk.services.iam.ServicePrincipal; 25 | import software.amazon.awscdk.services.lambda.AssetCode; 26 | import software.amazon.awscdk.services.lambda.Function; 27 | import software.amazon.awscdk.services.sns.Topic; 28 | import software.constructs.Construct; 29 | 30 | import javax.validation.constraints.NotBlank; 31 | import javax.validation.constraints.NotNull; 32 | import java.util.List; 33 | import java.util.Map; 34 | 35 | import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_ENV; 36 | import static software.amazon.awscdk.services.iam.ManagedPolicy.fromAwsManagedPolicyName; 37 | import static software.amazon.awscdk.services.lambda.Code.fromAsset; 38 | 39 | public class SpringNativeAwsLambdaStack extends ApiBaseStack { 40 | 41 | static final String LAMBDA_FUNCTION_ID = "spring-native-aws-lambda-function"; 42 | private static final String REST_API_ID = LAMBDA_FUNCTION_ID + "-rest-api"; 43 | private static final String DEAD_LETTER_TOPIC_ID = LAMBDA_FUNCTION_ID + "-dead-letter-topic"; 44 | private static final String LAMBDA_HANDLER = "org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest"; 45 | private static final String ENVIRONMENT_VARIABLE_SPRING_PROFILES_ACTIVE = "SPRING_PROFILES_ACTIVE"; 46 | 47 | public SpringNativeAwsLambdaStack( 48 | @NotNull final Construct scope, 49 | @NotBlank final String id, 50 | @NotBlank final String lambdaCodePath, 51 | @NotBlank final String stage, 52 | @NotNull final StackProps props) { 53 | 54 | super(scope, id, props); 55 | 56 | final Topic deadLetterTopic = createTopic(DEAD_LETTER_TOPIC_ID); 57 | 58 | final List managedPolicies = 59 | List.of(fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")); 60 | 61 | final Role role = Role.Builder.create(this, LAMBDA_FUNCTION_ID + "-role") 62 | .assumedBy(new ServicePrincipal("lambda.amazonaws.com")) 63 | .managedPolicies(managedPolicies) 64 | .build(); 65 | 66 | final AssetCode assetCode = fromAsset(lambdaCodePath); 67 | 68 | final Map environment = 69 | Map.of(ENVIRONMENT_VARIABLE_SPRING_PROFILES_ACTIVE, stage, KEY_ENV, stage); 70 | 71 | final Function function = createFunction( 72 | LAMBDA_FUNCTION_ID, 73 | LAMBDA_HANDLER, 74 | assetCode, 75 | deadLetterTopic, 76 | role, 77 | environment); 78 | 79 | createLambdaRestApi(stage, REST_API_ID, "name", "POST", function, true); 80 | } 81 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/StackUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import javax.validation.constraints.NotBlank; 22 | import javax.validation.constraints.NotEmpty; 23 | import lombok.AccessLevel; 24 | import lombok.NoArgsConstructor; 25 | import org.jetbrains.annotations.NotNull; 26 | import software.amazon.awscdk.App; 27 | import software.amazon.awscdk.DefaultStackSynthesizer; 28 | import software.amazon.awscdk.StackProps; 29 | 30 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 31 | public final class StackUtils { 32 | 33 | @NotNull 34 | public static SpringNativeAwsLambdaStack createStack( 35 | @NotNull final App app, 36 | @NotBlank final String stackName, 37 | @NotBlank final String lambdaCodePath, 38 | @NotBlank final String qualifier, 39 | @NotBlank final String fileAssetsBucketName, 40 | @NotEmpty final String stage) { 41 | final StackProps stackProps = createStackProps(stackName, qualifier, fileAssetsBucketName); 42 | return new SpringNativeAwsLambdaStack(app, stackName, lambdaCodePath, stage, stackProps); 43 | } 44 | 45 | @NotNull 46 | private static StackProps createStackProps(@NotBlank final String stackName, 47 | @NotBlank final String qualifier, 48 | @NotBlank final String fileAssetsBucketName) { 49 | return StackProps.builder() 50 | .synthesizer(createDefaultStackSynthesizer(qualifier, fileAssetsBucketName)) 51 | .stackName(stackName) 52 | .build(); 53 | } 54 | 55 | @NotNull 56 | private static DefaultStackSynthesizer createDefaultStackSynthesizer( 57 | @NotBlank final String qualifier, 58 | @NotBlank final String fileAssetsBucketName) { 59 | return DefaultStackSynthesizer.Builder.create() 60 | .qualifier(qualifier) 61 | .fileAssetsBucketName(fileAssetsBucketName) 62 | .build(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/TagUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import lombok.AccessLevel; 22 | import lombok.NoArgsConstructor; 23 | 24 | import javax.validation.constraints.NotBlank; 25 | import java.util.Map; 26 | 27 | import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE; 28 | import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_ENV; 29 | 30 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 31 | public final class TagUtils { 32 | 33 | public static Map createTags(@NotBlank final String env, 34 | @NotBlank final String costCentre) { 35 | return Map.of(KEY_ENV, env, KEY_COST_CENTRE, costCentre); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/lambda/CustomRuntime2023Function.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra.lambda; 20 | 21 | import com.coffeebeans.springnativeawslambda.infra.CoffeeBeansConstruct; 22 | import lombok.Getter; 23 | import org.jetbrains.annotations.NotNull; 24 | import software.amazon.awscdk.Duration; 25 | import software.amazon.awscdk.services.lambda.Function; 26 | import software.amazon.awscdk.services.lambda.FunctionProps; 27 | import software.constructs.Construct; 28 | 29 | import static com.google.common.base.Preconditions.checkArgument; 30 | import static org.apache.commons.collections4.MapUtils.isNotEmpty; 31 | import static org.apache.commons.lang3.StringUtils.isNoneBlank; 32 | import static software.amazon.awscdk.services.lambda.Runtime.PROVIDED_AL2023; 33 | 34 | /** 35 | * A lambda function with runtime provided.al2023 (custom runtime al2023). Creating function with any other 36 | * runtime (e.g. passed in the {@link FunctionProps} or via any other means will be ignored). 37 | */ 38 | 39 | @Getter 40 | public class CustomRuntime2023Function extends Construct implements CoffeeBeansConstruct { 41 | 42 | private static final int FUNCTION_DEFAULT_TIMEOUT_IN_SECONDS = 10; 43 | private static final int FUNCTION_DEFAULT_MEMORY_SIZE = 512; 44 | private static final int FUNCTION_DEFAULT_RETRY_ATTEMPTS = 2; 45 | 46 | private final software.amazon.awscdk.services.lambda.Function function; 47 | 48 | 49 | /** 50 | * @param scope This parameter is required. 51 | * @param id This parameter is required. 52 | * @param props This parameter is required. 53 | */ 54 | public CustomRuntime2023Function(@NotNull final Construct scope, 55 | @NotNull final String id, 56 | @NotNull final FunctionProps props) { 57 | super(scope, id); 58 | 59 | checkArgument(isNoneBlank(props.getHandler()), "'handler' is required"); 60 | checkArgument(isNoneBlank(props.getDescription()), "'description' is required"); 61 | checkArgument(isNotEmpty(props.getEnvironment()), "'environment' is required"); 62 | 63 | final Duration timeout = props.getTimeout() == null 64 | ? Duration.seconds(FUNCTION_DEFAULT_TIMEOUT_IN_SECONDS) 65 | : props.getTimeout(); 66 | 67 | final Number memorySize = props.getMemorySize() == null 68 | ? FUNCTION_DEFAULT_MEMORY_SIZE 69 | : props.getMemorySize(); 70 | 71 | final Number retryAttempts = props.getRetryAttempts() == null 72 | ? FUNCTION_DEFAULT_RETRY_ATTEMPTS 73 | : props.getRetryAttempts(); 74 | 75 | checkArgument(props.getMemorySize().intValue() >= 128 76 | && props.getMemorySize().intValue() <= 3008, 77 | "'memorySize' must be between 128 and 3008 (inclusive)"); 78 | 79 | checkArgument(props.getRetryAttempts().intValue() >= 0 80 | && props.getRetryAttempts().intValue() <= 2, 81 | "'retryAttempts' must be between 0 and 2 (inclusive)"); 82 | 83 | 84 | function = Function.Builder.create(this, id) 85 | .runtime(PROVIDED_AL2023) 86 | .functionName(props.getFunctionName()) 87 | .description(props.getDescription()) 88 | .code(props.getCode()) 89 | .handler(props.getHandler()) 90 | .role(props.getRole()) 91 | .vpc(props.getVpc()) 92 | .environment(props.getEnvironment()) 93 | .deadLetterTopic(props.getDeadLetterTopic()) 94 | .timeout(timeout) 95 | .memorySize(memorySize) 96 | .retryAttempts(retryAttempts) 97 | .build(); 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/ApiBaseStackTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | import static software.amazon.awscdk.services.ec2.Vpc.Builder.create; 23 | import static software.amazon.awscdk.services.iam.Role.fromRoleArn; 24 | import static software.amazon.awscdk.services.lambda.Runtime.PROVIDED_AL2023; 25 | import static software.amazon.awscdk.services.sns.Topic.fromTopicArn; 26 | import static software.amazon.awscdk.services.sqs.DeduplicationScope.MESSAGE_GROUP; 27 | 28 | import java.io.IOException; 29 | import java.nio.file.Path; 30 | import java.util.Map; 31 | import org.junit.jupiter.api.BeforeEach; 32 | import org.junit.jupiter.api.Test; 33 | import org.junit.jupiter.api.io.TempDir; 34 | import software.amazon.awscdk.App; 35 | import software.amazon.awscdk.services.apigateway.LambdaRestApi; 36 | import software.amazon.awscdk.services.ec2.Vpc; 37 | import software.amazon.awscdk.services.lambda.Code; 38 | import software.amazon.awscdk.services.lambda.Function; 39 | import software.amazon.awscdk.services.sns.Topic; 40 | import software.amazon.awscdk.services.sqs.DeadLetterQueue; 41 | import software.amazon.awscdk.services.sqs.Queue; 42 | 43 | class ApiBaseStackTest { 44 | 45 | private static final String ENV = "test"; 46 | 47 | private ApiBaseStack apiBaseStack; 48 | 49 | private final App app = new App(); 50 | 51 | private Path lambdaCodePath; 52 | 53 | @TempDir 54 | private static Path TEMP_DIR; 55 | 56 | @BeforeEach 57 | void setUp() throws IOException { 58 | 59 | lambdaCodePath = TestLambdaUtils.getTestLambdaCodePath(TEMP_DIR); 60 | 61 | this.apiBaseStack = StackUtils.createStack(app, "test-stack", lambdaCodePath.toString(), ENV, 62 | "test-cdk-bucket", ENV); 63 | } 64 | 65 | @Test 66 | void should_create_and_return_queue() { 67 | final String queueId = "test-queue"; 68 | 69 | final Queue actual = this.apiBaseStack.createQueue(queueId); 70 | 71 | assertThat(actual) 72 | .isNotNull() 73 | .hasFieldOrProperty("queueName") 74 | .hasFieldOrProperty("queueArn") 75 | .hasFieldOrProperty("queueUrl") 76 | .hasFieldOrProperty("deadLetterQueue") 77 | .extracting("fifo") 78 | .isEqualTo(false); 79 | } 80 | 81 | @Test 82 | void should_create_and_return_fifo_queue() { 83 | final String queueId = "test-queue"; 84 | 85 | final Queue actual = this.apiBaseStack.createFifoQueue(queueId, true, MESSAGE_GROUP); 86 | 87 | assertThat(actual) 88 | .isNotNull() 89 | .hasFieldOrProperty("queueName") 90 | .hasFieldOrProperty("queueArn") 91 | .hasFieldOrProperty("queueUrl") 92 | .hasFieldOrProperty("deadLetterQueue") 93 | .extracting("fifo") 94 | .isEqualTo(true); 95 | 96 | } 97 | 98 | @Test 99 | void should_create_and_return_dead_letter_queue() { 100 | final String deadLetterQueueId = "test-dead-letter-queue"; 101 | 102 | final DeadLetterQueue actual = this.apiBaseStack.createDeadLetterQueue(deadLetterQueueId); 103 | 104 | assertThat(actual) 105 | .isNotNull() 106 | .hasFieldOrProperty("maxReceiveCount") 107 | .hasFieldOrProperty("queue"); 108 | 109 | assertThat(actual.getMaxReceiveCount()) 110 | .isEqualTo(3); 111 | 112 | assertThat(actual.getQueue()) 113 | .hasFieldOrProperty("queueName") 114 | .hasFieldOrProperty("queueArn") 115 | .hasFieldOrProperty("queueUrl") 116 | .extracting("fifo") 117 | .isEqualTo(false); 118 | } 119 | 120 | @Test 121 | void should_create_and_return_fifo_dead_letter_queue() { 122 | final String deadLetterQueueId = "test-fifo-dead-letter-queue"; 123 | 124 | final DeadLetterQueue actual = this.apiBaseStack.createFifoDeadLetterQueue(deadLetterQueueId, 125 | true, MESSAGE_GROUP); 126 | 127 | assertThat(actual) 128 | .isNotNull() 129 | .hasFieldOrProperty("maxReceiveCount") 130 | .hasFieldOrProperty("queue"); 131 | 132 | assertThat(actual.getMaxReceiveCount()) 133 | .isEqualTo(3); 134 | 135 | assertThat(actual.getQueue()) 136 | .isNotNull() 137 | .hasFieldOrProperty("queueName") 138 | .hasFieldOrProperty("queueArn") 139 | .hasFieldOrProperty("queueUrl") 140 | .extracting("fifo") 141 | .isEqualTo(true); 142 | } 143 | 144 | @Test 145 | void should_create_and_return_topic() { 146 | final String topicId = "test-topic"; 147 | 148 | final Topic actual = this.apiBaseStack.createTopic(topicId); 149 | 150 | assertThat(actual) 151 | .isNotNull() 152 | .hasFieldOrProperty("topicName") 153 | .hasFieldOrProperty("topicArn") 154 | .extracting("fifo") 155 | .isEqualTo(false); 156 | } 157 | 158 | @Test 159 | void should_create_and_return_fifo_topic() { 160 | final String topicId = "test-topic"; 161 | 162 | final Topic actual = this.apiBaseStack.createFifoTopic(topicId, true, true); 163 | 164 | assertThat(actual) 165 | .isNotNull() 166 | .hasFieldOrProperty("topicName") 167 | .hasFieldOrProperty("topicArn") 168 | .extracting("fifo") 169 | .isEqualTo(true); 170 | } 171 | 172 | @Test 173 | void should_create_and_return_lambda_function() { 174 | final Vpc vpc = create(this.apiBaseStack, "test-vpc").build(); 175 | 176 | final Function actual = this.apiBaseStack.createFunction(vpc, 177 | "test-function", 178 | "com.coffeebeans.springnativeawslambda.infra.lambda.CustomRuntime2Function::handleRequest", 179 | Code.fromAsset(this.lambdaCodePath.toString()), 180 | fromTopicArn(this.apiBaseStack, "success-topic", "arn:aws:sns:us-east-1:***:success-topic"), 181 | fromRoleArn(this.apiBaseStack, "test-role", "arn:aws:iam::***:role/test-role"), 182 | Map.of("Account", "***")); 183 | 184 | assertThat(actual) 185 | .isNotNull() 186 | .hasFieldOrProperty("functionArn") 187 | .hasFieldOrProperty("role") 188 | .hasFieldOrProperty("functionName") 189 | .hasFieldOrProperty("functionArn") 190 | .hasFieldOrProperty("env") 191 | .hasFieldOrProperty("architecture") 192 | .hasFieldOrProperty("runtime") 193 | .hasFieldOrProperty("timeout"); 194 | 195 | assertThat(actual.getRuntime()) 196 | .isEqualTo(PROVIDED_AL2023); 197 | } 198 | 199 | @Test 200 | void should_create_and_return_lambda_rest_api() { 201 | final Vpc vpc = create(this.apiBaseStack, "test-vpc").build(); 202 | 203 | final Function function = this.apiBaseStack.createFunction(vpc, 204 | "test-function", 205 | "com.coffeebeans.springnativeawslambda.infra.lambda.CustomRuntime2Function::handleRequest", 206 | Code.fromAsset(this.lambdaCodePath.toString()), 207 | fromTopicArn(this.apiBaseStack, "success-topic", "arn:aws:sns:us-east-1:***:success-topic"), 208 | fromRoleArn(this.apiBaseStack, "test-role", "arn:aws:iam::***:role/test-role"), 209 | Map.of("Account", "***")); 210 | 211 | final LambdaRestApi actual = this.apiBaseStack.createLambdaRestApi("test", "rest-api", "name", 212 | "POST", function, false); 213 | 214 | assertThat(actual) 215 | .isNotNull() 216 | .hasFieldOrProperty("deploymentStage") 217 | .hasFieldOrProperty("env") 218 | .hasFieldOrProperty("restApiName") 219 | .hasFieldOrProperty("root") 220 | .hasFieldOrProperty("url") 221 | .hasFieldOrProperty("restApiRootResourceId") 222 | .hasFieldOrProperty("restApiId") 223 | .hasFieldOrProperty("methods"); 224 | } 225 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/LambdaTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import com.fasterxml.jackson.core.JsonProcessingException; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | import static cloud.pianola.cdk.fluent.assertion.CDKStackAssert.assertThat; 28 | import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE; 29 | import static software.amazon.awscdk.assertions.Match.exact; 30 | import static software.amazon.awscdk.assertions.Match.stringLikeRegexp; 31 | 32 | class LambdaTest extends TemplateSupport { 33 | 34 | public static final String TEST = "test"; 35 | 36 | @Test 37 | void should_have_lambda_function() { 38 | 39 | assertThat(template) 40 | .containsFunction("spring-native-aws-lambda-function") 41 | .hasHandler("org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest") 42 | .hasCode("test-cdk-bucket", "(.*).zip") 43 | .hasRole("springnativeawslambdafunctionrole(.*)") 44 | .hasDependency("springnativeawslambdafunctionrole(.*)") 45 | .hasDependency("springnativeawslambdafunctionroleDefaultPolicy(.*)") 46 | .hasTag("COST_CENTRE", KEY_COST_CENTRE) 47 | .hasTag("ENV", TEST) 48 | .hasEnvironmentVariable("ENV", TEST) 49 | .hasEnvironmentVariable("SPRING_PROFILES_ACTIVE", TEST) 50 | .hasDescription("Lambda example with spring native") 51 | .hasMemorySize(512) 52 | .hasRuntime("provided.al2023") 53 | .hasTimeout(3); 54 | } 55 | 56 | @Test 57 | void should_have_role_with_AWSLambdaBasicExecutionRole_policy_to_assume_by_lambda() { 58 | final String principal = "lambda.amazonaws.com"; 59 | final String effect = "Allow"; 60 | final String policyDocumentVersion = "2012-10-17"; 61 | final String managedPolicyArn = ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"; 62 | 63 | assertThat(template) 64 | .containsRoleWithManagedPolicyArn(managedPolicyArn) 65 | .hasAssumeRolePolicyDocument(principal, null, effect, policyDocumentVersion, 66 | "sts:AssumeRole"); 67 | } 68 | 69 | @Test 70 | void should_have_default_policy_to_allow_lambda_publish_to_sns() throws JsonProcessingException { 71 | 72 | final String policyName = "springnativeawslambdafunctionroleDefaultPolicy(.*)"; 73 | final String deadLetterTopic = "springnativeawslambdafunctiondeadlettertopic(.*)"; 74 | final String action = "sns:Publish"; 75 | final String effect = "Allow"; 76 | final String policyDocumentVersion = "2012-10-17"; 77 | 78 | assertThat(template) 79 | .containsPolicy(policyName) 80 | .isAssociatedWithRole("springnativeawslambdafunctionrole(.*)") 81 | .hasPolicyDocumentVersion(policyDocumentVersion) 82 | .hasPolicyDocumentStatement(null, 83 | deadLetterTopic, 84 | action, 85 | effect, 86 | policyDocumentVersion); 87 | } 88 | 89 | @Test 90 | void should_have_event_invoke_config_for_success_and_failure() { 91 | 92 | final String functionName = "springnativeawslambdafunction(.*)"; 93 | 94 | assertThat(template) 95 | .containsLambdaEventInvokeConfig(functionName) 96 | .hasLambdaEventInvokeConfigQualifier("$LATEST") 97 | .hasLambdaEventInvokeConfigMaximumRetryAttempts(2); 98 | } 99 | 100 | @Test 101 | void should_have_permission_to_allow_rest_api_root_call_lambda() { 102 | 103 | final List sourceArn = List.of( 104 | "arn:", 105 | Map.of("Ref", exact("AWS::Partition")), 106 | ":execute-api:", 107 | Map.of("Ref", exact("AWS::Region")), 108 | ":", 109 | Map.of("Ref", exact("AWS::AccountId")), 110 | ":", 111 | Map.of("Ref", stringLikeRegexp("springnativeawslambdafunctionrestapi(.*)")), 112 | "/", 113 | Map.of("Ref", stringLikeRegexp("springnativeawslambdafunctionrestapi(.*)")), 114 | "/*/" 115 | ); 116 | 117 | final String action = "lambda:InvokeFunction"; 118 | final String principal = "apigateway.amazonaws.com"; 119 | final String functionName = "springnativeawslambdafunction(.*)"; 120 | 121 | assertThat(template) 122 | .containsLambdaPermission(functionName, action, principal, sourceArn); 123 | } 124 | 125 | @Test 126 | void should_have_permission_to_allow_rest_api_root_test_call_lambda() { 127 | 128 | final List sourceArn = List.of( 129 | "arn:", 130 | Map.of("Ref", exact("AWS::Partition")), 131 | ":execute-api:", 132 | Map.of("Ref", exact("AWS::Region")), 133 | ":", 134 | Map.of("Ref", exact("AWS::AccountId")), 135 | ":", 136 | Map.of("Ref", stringLikeRegexp("springnativeawslambdafunctionrestapi(.*)")), 137 | "/test-invoke-stage/*/*" 138 | ); 139 | 140 | final String action = "lambda:InvokeFunction"; 141 | final String principal = "apigateway.amazonaws.com"; 142 | final String functionName = "springnativeawslambdafunction(.*)"; 143 | 144 | assertThat(template) 145 | .containsLambdaPermission(functionName, action, principal, sourceArn); 146 | } 147 | 148 | @Test 149 | void should_have_permission_to_allow_rest_api_proxy_to_call_lambda() { 150 | 151 | final List sourceArn = List.of( 152 | "arn:", 153 | Map.of("Ref", exact("AWS::Partition")), 154 | ":execute-api:", 155 | Map.of("Ref", exact("AWS::Region")), 156 | ":", 157 | Map.of("Ref", exact("AWS::AccountId")), 158 | ":", 159 | Map.of("Ref", stringLikeRegexp("springnativeawslambdafunctionrestapi(.*)")), 160 | "/", 161 | Map.of("Ref", 162 | stringLikeRegexp("springnativeawslambdafunctionrestapiDeploymentStagetest(.*)")), 163 | "/*/*" 164 | ); 165 | 166 | final String action = "lambda:InvokeFunction"; 167 | final String principal = "apigateway.amazonaws.com"; 168 | final String functionName = "springnativeawslambdafunction(.*)"; 169 | 170 | assertThat(template) 171 | .containsLambdaPermission(functionName, action, principal, sourceArn); 172 | } 173 | 174 | @Test 175 | void should_have_permission_to_allow_rest_api_proxy_test_to_call_lambda() { 176 | 177 | final List sourceArn = List.of( 178 | "arn:", 179 | Map.of("Ref", exact("AWS::Partition")), 180 | ":execute-api:", 181 | Map.of("Ref", exact("AWS::Region")), 182 | ":", 183 | Map.of("Ref", exact("AWS::AccountId")), 184 | ":", 185 | Map.of("Ref", stringLikeRegexp("springnativeawslambdafunctionrestapi(.*)")), 186 | "/test-invoke-stage/*/*" 187 | ); 188 | 189 | final String action = "lambda:InvokeFunction"; 190 | final String principal = "apigateway.amazonaws.com"; 191 | final String functionName = "springnativeawslambdafunction(.*)"; 192 | 193 | assertThat(template) 194 | .containsLambdaPermission(functionName, action, principal, sourceArn); 195 | } 196 | 197 | @Test 198 | void should_have_permission_to_allow_post_rest_api_method_to_call_lambda() { 199 | 200 | final List sourceArn = List.of( 201 | "arn:", 202 | Map.of("Ref", exact("AWS::Partition")), 203 | ":execute-api:", 204 | Map.of("Ref", exact("AWS::Region")), 205 | ":", 206 | Map.of("Ref", exact("AWS::AccountId")), 207 | ":", 208 | Map.of("Ref", stringLikeRegexp("springnativeawslambdafunctionrestapi(.*)")), 209 | "/", 210 | Map.of("Ref", 211 | stringLikeRegexp("springnativeawslambdafunctionrestapiDeploymentStagetest(.*)")), 212 | "/POST/name" 213 | ); 214 | 215 | final String action = "lambda:InvokeFunction"; 216 | final String principal = "apigateway.amazonaws.com"; 217 | final String functionName = "springnativeawslambdafunction(.*)"; 218 | 219 | assertThat(template) 220 | .containsLambdaPermission(functionName, action, principal, sourceArn); 221 | } 222 | 223 | @Test 224 | void should_have_permission_to_allow_post_rest_api_method_test_to_call_lambda() { 225 | 226 | final List sourceArn = List.of( 227 | "arn:", 228 | Map.of("Ref", exact("AWS::Partition")), 229 | ":execute-api:", 230 | Map.of("Ref", exact("AWS::Region")), 231 | ":", 232 | Map.of("Ref", exact("AWS::AccountId")), 233 | ":", 234 | Map.of("Ref", stringLikeRegexp("springnativeawslambdafunctionrestapi(.*)")), 235 | "/test-invoke-stage/POST/name" 236 | ); 237 | 238 | final String action = "lambda:InvokeFunction"; 239 | final String principal = "apigateway.amazonaws.com"; 240 | final String functionName = "springnativeawslambdafunction(.*)"; 241 | 242 | assertThat(template) 243 | .containsLambdaPermission(functionName, action, principal, sourceArn); 244 | } 245 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/RestApiTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import org.junit.jupiter.api.Test; 22 | import software.amazon.awscdk.assertions.Match; 23 | 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | import static cloud.pianola.cdk.fluent.assertion.CDKStackAssert.assertThat; 28 | import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE; 29 | 30 | class RestApiTest extends TemplateSupport { 31 | 32 | public static final String TEST = "test"; 33 | 34 | @Test 35 | void should_have_rest_api() { 36 | 37 | assertThat(template) 38 | .containsRestApi("spring-native-aws-lambda-function-rest-api") 39 | .hasTag("COST_CENTRE", KEY_COST_CENTRE) 40 | .hasTag("ENV", TEST); 41 | } 42 | 43 | @Test 44 | void should_have_rest_api_account() { 45 | final String cloudWatchRoleArn = "springnativeawslambdafunctionrestapiCloudWatchRole(.*)"; 46 | final String dependency = "springnativeawslambdafunctionrestapi(.*)"; 47 | 48 | assertThat(template) 49 | .containsRestApiAccountWithCloudWatchRoleArn(cloudWatchRoleArn) 50 | .hasDependency(dependency) 51 | .hasUpdateReplacePolicy("Retain") 52 | .hasDeletionPolicy("Retain"); 53 | } 54 | 55 | @Test 56 | void should_have_rest_api_deployment() { 57 | 58 | assertThat(template) 59 | .containsRestApiDeployment("springnativeawslambdafunctionrestapi(.*)") 60 | .hasDependency("springnativeawslambdafunctionrestapiproxyANY(.*)") 61 | .hasDependency("springnativeawslambdafunctionrestapiproxy(.*)") 62 | .hasDependency("springnativeawslambdafunctionrestapiANY(.*)") 63 | .hasDependency("springnativeawslambdafunctionrestapinamePOST(.*)") 64 | .hasDependency("springnativeawslambdafunctionrestapiname(.*)") 65 | .hasDescription("Automatically created by the RestApi construct"); 66 | } 67 | 68 | @Test 69 | void should_have_rest_api_stage() { 70 | 71 | assertThat(template) 72 | .containsRestApiStage("test") 73 | .hasRestApiId(("springnativeawslambdafunctionrestapi(.*)")) 74 | .hasDeploymentId(("springnativeawslambdafunctionrestapiDeployment(.*)")) 75 | .hasDependency("springnativeawslambdafunctionrestapiAccount(.*)") 76 | .hasTag("COST_CENTRE", KEY_COST_CENTRE) 77 | .hasTag("ENV", TEST); 78 | } 79 | 80 | @Test 81 | void should_have_proxy_resource() { 82 | final String restApiId = "springnativeawslambdafunctionrestapi(.*)"; 83 | 84 | final Map> parentId = Map.of( 85 | "Fn::GetAtt", List.of(Match.stringLikeRegexp(restApiId), "RootResourceId") 86 | ); 87 | 88 | assertThat(template) 89 | .containsRestApiResource("{proxy+}", restApiId, parentId) 90 | .hasRestApiId(restApiId) 91 | .hasParentId(restApiId); 92 | } 93 | 94 | @Test 95 | void should_have_account_resource() { 96 | final String restApiId = "springnativeawslambdafunctionrestapi(.*)"; 97 | 98 | final Map> parentId = Map.of( 99 | "Fn::GetAtt", List.of(Match.stringLikeRegexp(restApiId), "RootResourceId") 100 | ); 101 | 102 | assertThat(template) 103 | .containsRestApiResource("name", restApiId, parentId) 104 | .hasRestApiId(restApiId) 105 | .hasParentId(restApiId); 106 | } 107 | 108 | @Test 109 | void should_have_post_method() { 110 | 111 | final String integrationType = "AWS_PROXY"; 112 | final String httpMethod = "POST"; 113 | 114 | assertThat(template) 115 | .containsNonRootRestApiMethod(httpMethod, "springnativeawslambdafunctionrestapiname(.*)") 116 | .hasHttpMethod(httpMethod) 117 | .hasIntegration(httpMethod, integrationType) 118 | .hasAuthorizationType("NONE") 119 | .hasRestApiId(("springnativeawslambdafunctionrestapi(.*)")); 120 | } 121 | 122 | @Test 123 | void should_have_proxy_method() { 124 | 125 | final String method = "ANY"; 126 | final String integrationType = "AWS_PROXY"; 127 | 128 | assertThat(template) 129 | .containsNonRootRestApiMethod(method, "springnativeawslambdafunctionrestapiproxy(.*)") 130 | .hasHttpMethod(method) 131 | .hasIntegration("POST", integrationType) 132 | .hasAuthorizationType("NONE") 133 | .hasRestApiId(("springnativeawslambdafunctionrestapi(.*)")); 134 | } 135 | 136 | @Test 137 | void should_have_root_method() { 138 | 139 | final String method = "ANY"; 140 | final String integrationType = "AWS_PROXY"; 141 | 142 | assertThat(template) 143 | .containsRootRestApiMethod(method, "springnativeawslambdafunctionrestapi(.*)") 144 | .hasHttpMethod(method) 145 | .hasIntegration("POST", integrationType) 146 | .hasAuthorizationType("NONE") 147 | .hasRestApiId(("springnativeawslambdafunctionrestapi(.*)")); 148 | } 149 | 150 | @Test 151 | void should_have_role_with_AmazonAPIGatewayPushToCloudWatchLogs_policy_for_rest_api_to_push_logs_to_cloud_watch() { 152 | final String principal = "apigateway.amazonaws.com"; 153 | final String effect = "Allow"; 154 | final String policyDocumentVersion = "2012-10-17"; 155 | final String managedPolicyArn = ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"; 156 | 157 | assertThat(template) 158 | .containsRoleWithManagedPolicyArn(managedPolicyArn) 159 | .hasAssumeRolePolicyDocument(principal, 160 | null, 161 | effect, 162 | policyDocumentVersion, 163 | "sts:AssumeRole"); 164 | } 165 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TagUtilsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import org.junit.jupiter.api.Test; 22 | 23 | import java.util.Map; 24 | 25 | import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE; 26 | import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_ENV; 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | 29 | class TagUtilsTest { 30 | 31 | @Test 32 | void should_create_and_return_tag_map() { 33 | // given 34 | final String env = "test"; 35 | final String costCentre = "coffeeBeans-core"; 36 | 37 | // when 38 | final Map tags = TagUtils.createTags(env, costCentre); 39 | 40 | assertThat(tags) 41 | .containsEntry(KEY_ENV, env) 42 | .containsEntry(KEY_COST_CENTRE, costCentre); 43 | } 44 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TemplateSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import org.junit.jupiter.api.AfterAll; 22 | import org.junit.jupiter.api.BeforeAll; 23 | import org.junit.jupiter.api.io.TempDir; 24 | import software.amazon.awscdk.App; 25 | import software.amazon.awscdk.Tags; 26 | import software.amazon.awscdk.assertions.Template; 27 | 28 | import java.io.IOException; 29 | import java.nio.file.Path; 30 | import java.util.Map; 31 | import java.util.Objects; 32 | 33 | import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE; 34 | import static com.coffeebeans.springnativeawslambda.infra.TagUtils.createTags; 35 | 36 | public abstract class TemplateSupport { 37 | 38 | public static final String ENV = "test"; 39 | public static final String TEST_CDK_BUCKET = "test-cdk-bucket"; 40 | public static final String QUALIFIER = "test"; 41 | static Template template; 42 | private static final String STACK_NAME = "spring-native-aws-lambda-function-test-stack"; 43 | @TempDir 44 | private static Path TEMP_DIR; 45 | 46 | @BeforeAll 47 | static void initAll() throws IOException { 48 | final Path lambdaCodePath = TestLambdaUtils.getTestLambdaCodePath(TEMP_DIR); 49 | 50 | final Map tags = createTags(ENV, KEY_COST_CENTRE); 51 | final App app = new App(); 52 | final SpringNativeAwsLambdaStack stack = StackUtils.createStack(app, STACK_NAME, 53 | lambdaCodePath.toString(), QUALIFIER, TEST_CDK_BUCKET, ENV); 54 | 55 | tags.entrySet().stream() 56 | .filter(tag -> Objects.nonNull(tag.getValue())) 57 | .forEach(tag -> Tags.of(app).add(tag.getKey(), tag.getValue())); 58 | 59 | template = Template.fromStack(stack); 60 | } 61 | 62 | @AfterAll 63 | static void cleanup() { 64 | template = null; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TestLambdaUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.nio.file.Path; 24 | import javax.validation.constraints.NotNull; 25 | import lombok.AccessLevel; 26 | import lombok.NoArgsConstructor; 27 | 28 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 29 | public final class TestLambdaUtils { 30 | 31 | @NotNull 32 | public static Path getTestLambdaCodePath(@NotNull final Path tempDir) throws IOException { 33 | final Path lambdaCodePath = tempDir.resolve("lambda-package.zip"); 34 | 35 | final File file = lambdaCodePath.toFile(); 36 | 37 | if (file.exists()) { 38 | return lambdaCodePath; 39 | } 40 | 41 | final boolean isCreated = file.createNewFile(); 42 | 43 | if (!isCreated) { 44 | throw new IOException("Failed to create lambda package"); 45 | } 46 | return lambdaCodePath; 47 | } 48 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TopicTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Muhammad Hamadto 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * See the NOTICE file distributed with this work for additional information regarding copyright ownership. 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package com.coffeebeans.springnativeawslambda.infra; 20 | 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static cloud.pianola.cdk.fluent.assertion.CDKStackAssert.assertThat; 24 | import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE; 25 | 26 | class TopicTest extends TemplateSupport { 27 | 28 | public static final String TEST = "test"; 29 | 30 | @Test 31 | void should_have_dead_letter_topic() { 32 | assertThat(template) 33 | .containsTopic("spring-native-aws-lambda-function-dead-letter-topic") 34 | .hasTag("COST_CENTRE", KEY_COST_CENTRE) 35 | .hasTag("ENV", TEST); 36 | } 37 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/lambda/CustomRuntime2023FunctionTest.java: -------------------------------------------------------------------------------- 1 | package com.coffeebeans.springnativeawslambda.infra.lambda; 2 | 3 | import com.coffeebeans.springnativeawslambda.infra.TestLambdaUtils; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.io.TempDir; 9 | import software.amazon.awscdk.App; 10 | import software.amazon.awscdk.Duration; 11 | import software.amazon.awscdk.Size; 12 | import software.amazon.awscdk.Stack; 13 | import software.amazon.awscdk.services.codeguruprofiler.ProfilingGroup; 14 | import software.amazon.awscdk.services.ec2.SecurityGroup; 15 | import software.amazon.awscdk.services.ec2.SubnetSelection; 16 | import software.amazon.awscdk.services.ec2.Vpc; 17 | import software.amazon.awscdk.services.iam.PolicyStatement; 18 | import software.amazon.awscdk.services.iam.Role; 19 | import software.amazon.awscdk.services.kms.Key; 20 | import software.amazon.awscdk.services.lambda.Code; 21 | import software.amazon.awscdk.services.lambda.Function; 22 | import software.amazon.awscdk.services.lambda.FunctionProps; 23 | import software.amazon.awscdk.services.lambda.Tracing; 24 | import software.amazon.awscdk.services.lambda.VersionOptions; 25 | import software.amazon.awscdk.services.lambda.destinations.SnsDestination; 26 | import software.amazon.awscdk.services.lambda.eventsources.ApiEventSource; 27 | import software.amazon.awscdk.services.logs.RetentionDays; 28 | 29 | import java.io.IOException; 30 | import java.nio.file.Path; 31 | import java.util.Collections; 32 | import java.util.List; 33 | import java.util.Map; 34 | 35 | import static org.assertj.core.api.Assertions.assertThat; 36 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 37 | import static software.amazon.awscdk.services.lambda.Architecture.ARM_64; 38 | import static software.amazon.awscdk.services.lambda.CodeSigningConfig.fromCodeSigningConfigArn; 39 | import static software.amazon.awscdk.services.lambda.Runtime.PROVIDED_AL2; 40 | import static software.amazon.awscdk.services.lambda.Runtime.PROVIDED_AL2023; 41 | import static software.amazon.awscdk.services.sns.Topic.fromTopicArn; 42 | 43 | class CustomRuntime2023FunctionTest { 44 | 45 | public static final @NotNull Duration DEFAULT_TIMEOUT = Duration.seconds(10); 46 | @TempDir 47 | private static Path TEMP_DIR; 48 | private Stack stack; 49 | private String id; 50 | private SnsDestination onFailure; 51 | private SnsDestination onSuccess; 52 | private Path lambdaCodePath; 53 | private FunctionProps.Builder functionPropsBuilder; 54 | 55 | @BeforeEach 56 | void setUp() throws IOException { 57 | lambdaCodePath = TestLambdaUtils.getTestLambdaCodePath(TEMP_DIR); 58 | 59 | stack = new Stack(new App(), "test-stack"); 60 | id = "test-function"; 61 | 62 | onFailure = new SnsDestination( 63 | fromTopicArn(stack, "failure-topic", "arn:aws:sns:us-east-1:***:failure-topic")); 64 | onSuccess = new SnsDestination( 65 | fromTopicArn(stack, "success-topic", "arn:aws:sns:us-east-1:***:success-topic")); 66 | 67 | functionPropsBuilder = FunctionProps.builder() 68 | .runtime(PROVIDED_AL2023) 69 | .maxEventAge(Duration.seconds(514)) 70 | .onFailure(onFailure) 71 | .onSuccess(onSuccess) 72 | .retryAttempts(2) 73 | .allowPublicSubnet(false) 74 | .architecture(ARM_64) 75 | .codeSigningConfig(fromCodeSigningConfigArn(stack, "test-code-signing-config", 76 | "arn:aws:lambda:us-east-1:***:code-signing-config:***")) 77 | .currentVersionOptions(VersionOptions.builder().build()) 78 | .deadLetterQueue(null) 79 | .deadLetterQueueEnabled(false) 80 | .deadLetterTopic(null) 81 | .description("test function") 82 | .environment(Map.of("Account", "***")) 83 | .environmentEncryption( 84 | Key.fromKeyArn(stack, "test-key", "arn:aws:kms:us-east-1:***:key/***")) 85 | .ephemeralStorageSize(Size.gibibytes(1)) 86 | .events(List.of(new ApiEventSource("POST", "/test"))) 87 | .filesystem(null) 88 | .functionName("test-function") 89 | .initialPolicy(List.of(PolicyStatement.Builder.create().build())) 90 | .insightsVersion(null) 91 | .layers(Collections.emptyList()) 92 | .logRetention(RetentionDays.FIVE_DAYS) 93 | .logRetentionRole( 94 | Role.fromRoleArn(stack, "test-log-role", "arn:aws:iam::***:role/test-log-role")) 95 | .memorySize(512) 96 | .profiling(false) 97 | .profilingGroup(ProfilingGroup.fromProfilingGroupName(stack, "test-profiling-group", 98 | "test-profiling-group")) 99 | .reservedConcurrentExecutions(2) 100 | .role(Role.fromRoleArn(stack, "test-role", "arn:aws:iam::***:role/test-role")) 101 | .securityGroups( 102 | List.of(SecurityGroup.fromSecurityGroupId(stack, "test-security-group", "sg-***"))) 103 | .tracing(Tracing.ACTIVE) 104 | .vpc(Vpc.Builder.create(stack, "test-vpc").build()) 105 | .vpcSubnets(SubnetSelection.builder().build()) 106 | .code(Code.fromAsset(lambdaCodePath.toString())) 107 | .handler( 108 | "com.coffeebeans.springnativeawslambda.infra.lambda.CustomRuntime2Function::handleRequest"); 109 | } 110 | 111 | @Test 112 | void should_create_and_return_function() { 113 | final FunctionProps functionProps = functionPropsBuilder.build(); 114 | final Function actual = new CustomRuntime2023Function(stack, id, functionProps).getFunction(); 115 | 116 | assertThat(actual) 117 | .isNotNull(); 118 | 119 | assertThat(actual.getRuntime()) 120 | .isEqualTo(PROVIDED_AL2023); 121 | 122 | assertThat(actual.getTimeout() 123 | .toSeconds()) 124 | .isEqualTo(DEFAULT_TIMEOUT.toSeconds()); 125 | } 126 | 127 | @Test 128 | void should_not_override_provided_al2023() { 129 | final FunctionProps functionProps = functionPropsBuilder 130 | .runtime(PROVIDED_AL2) 131 | .build(); 132 | final Function actual = new CustomRuntime2023Function(stack, id, functionProps).getFunction(); 133 | 134 | assertThat(actual) 135 | .isNotNull(); 136 | 137 | assertThat(actual.getRuntime()) 138 | .isEqualTo(PROVIDED_AL2023); 139 | } 140 | 141 | @Test 142 | void should_create_and_return_function_when_with_non_default_timeout() { 143 | final Duration timeout = Duration.seconds(3); 144 | final FunctionProps functionProps = functionPropsBuilder 145 | .timeout(timeout) 146 | .build(); 147 | final Function actual = new CustomRuntime2023Function(stack, id, functionProps).getFunction(); 148 | 149 | assertThat(actual) 150 | .isNotNull(); 151 | 152 | assertThat(actual.getTimeout() 153 | .toSeconds()) 154 | .isEqualTo(3); 155 | } 156 | 157 | @Test 158 | void should_throw_exception_when_function_handler_is_empty_string() { 159 | final FunctionProps functionProps = functionPropsBuilder 160 | .handler(StringUtils.EMPTY) 161 | .build(); 162 | 163 | assertThatThrownBy(() -> new CustomRuntime2023Function(stack, id, functionProps)) 164 | .isNotNull() 165 | .isInstanceOf(IllegalArgumentException.class) 166 | .hasFieldOrPropertyWithValue("message", "'handler' is required"); 167 | } 168 | 169 | @Test 170 | void should_throw_exception_when_function_description_is_missing() { 171 | final FunctionProps functionProps = functionPropsBuilder 172 | .description(null) 173 | .build(); 174 | 175 | assertThatThrownBy(() -> new CustomRuntime2023Function(stack, id, functionProps)) 176 | .isNotNull() 177 | .isInstanceOf(IllegalArgumentException.class) 178 | .hasFieldOrPropertyWithValue("message", "'description' is required"); 179 | } 180 | 181 | @Test 182 | void should_throw_exception_when_function_description_is_empty_string() { 183 | final FunctionProps functionProps = functionPropsBuilder 184 | .description(StringUtils.EMPTY) 185 | .build(); 186 | 187 | assertThatThrownBy(() -> new CustomRuntime2023Function(stack, id, functionProps)) 188 | .isNotNull() 189 | .isInstanceOf(IllegalArgumentException.class) 190 | .hasFieldOrPropertyWithValue("message", "'description' is required"); 191 | } 192 | 193 | @Test 194 | void should_throw_exception_when_function_environment_is_empty_map() { 195 | final FunctionProps functionProps = functionPropsBuilder 196 | .environment(Collections.emptyMap()) 197 | .build(); 198 | 199 | assertThatThrownBy(() -> new CustomRuntime2023Function(stack, id, functionProps)) 200 | .isNotNull() 201 | .isInstanceOf(IllegalArgumentException.class) 202 | .hasFieldOrPropertyWithValue("message", "'environment' is required"); 203 | } 204 | 205 | @Test 206 | void should_throw_exception_when_function_environment_is_null() { 207 | final FunctionProps functionProps = functionPropsBuilder 208 | .environment(null) 209 | .build(); 210 | 211 | assertThatThrownBy(() -> new CustomRuntime2023Function(stack, id, functionProps)) 212 | .isNotNull() 213 | .isInstanceOf(IllegalArgumentException.class) 214 | .hasFieldOrPropertyWithValue("message", "'environment' is required"); 215 | } 216 | 217 | @Test 218 | void should_throw_exception_when_function_memory_size_is_less_than_128() { 219 | final FunctionProps functionProps = functionPropsBuilder 220 | .memorySize(120) 221 | .build(); 222 | 223 | assertThatThrownBy(() -> new CustomRuntime2023Function(stack, id, functionProps)) 224 | .isNotNull() 225 | .isInstanceOf(IllegalArgumentException.class) 226 | .hasFieldOrPropertyWithValue("message", 227 | "'memorySize' must be between 128 and 3008 (inclusive)"); 228 | } 229 | 230 | @Test 231 | void should_throw_exception_when_function_memory_size_is_larger_than_3008() { 232 | final FunctionProps functionProps = functionPropsBuilder 233 | .memorySize(3500) 234 | .build(); 235 | 236 | assertThatThrownBy(() -> new CustomRuntime2023Function(stack, id, functionProps)) 237 | .isNotNull() 238 | .isInstanceOf(IllegalArgumentException.class) 239 | .hasFieldOrPropertyWithValue("message", 240 | "'memorySize' must be between 128 and 3008 (inclusive)"); 241 | } 242 | 243 | @Test 244 | void should_throw_exception_when_function_memory_size_is_less_than_zero() { 245 | final FunctionProps functionProps = functionPropsBuilder 246 | .retryAttempts(-1) 247 | .build(); 248 | 249 | assertThatThrownBy(() -> new CustomRuntime2023Function(stack, id, functionProps)) 250 | .isNotNull() 251 | .isInstanceOf(IllegalArgumentException.class) 252 | .hasFieldOrPropertyWithValue("message", 253 | "'retryAttempts' must be between 0 and 2 (inclusive)"); 254 | } 255 | 256 | @Test 257 | void should_throw_exception_when_function_memory_size_is_larger_than_two() { 258 | final FunctionProps functionProps = functionPropsBuilder 259 | .retryAttempts(3) 260 | .build(); 261 | 262 | assertThatThrownBy(() -> new CustomRuntime2023Function(stack, id, functionProps)) 263 | .isNotNull() 264 | .isInstanceOf(IllegalArgumentException.class) 265 | .hasFieldOrPropertyWithValue("message", 266 | "'retryAttempts' must be between 0 and 2 (inclusive)"); 267 | } 268 | } -------------------------------------------------------------------------------- /spring-native-aws-lambda-infra/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline --------------------------------------------------------------------------------