spring-native-aws-lambda
                                
                                 spring-native-aws-lambda copied to clipboard
                                
                                    spring-native-aws-lambda copied to clipboard
                            
                            
                            
                        Simple AWS lambda using Spring Native and Spring Cloud Functions.
spring-native-aws-lambda
| Component | Version | 
|---|---|
| JDK | 21 | 
| Spring Cloud | 2023.0.0 | 
| Spring Boot | 3.2.1 | 
Test
$ sdk use java 21.0.1-graal
$ ./mvnw -ntp clean verify -U
Building and Running
Locally
Using docker-compose
- Run the following commands
$ docker-compose up
- Make a call
 The service responds$ curl --location --request POST 'http://localhost:4566/restapis/<restApiId>/compose/_user_request_/somePathId' \ --header 'Content-Type: application/json' \ --data-raw '{ "body": "{ \"name\": \"CoffeeBeans\" }" }'[ { "name": "CoffeeBeans", "saved": true } ]
Using mvnw
- Run the following commands
 The service starts in less than 100 ms$ export SPRING_PROFILES_ACTIVE=local $ ./mvnw -ntp clean -Pnative -DskipTests native:compile package -pl spring-native-aws-lambda-function $ ./spring-native-aws-lambda-function/target/spring-native-aws-lambda-function2022-12-07 02:56:51.706 INFO 42417 --- [ main] c.c.s.Application : Starting Application using Java 17.0.4 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" 2022-12-07 02:56:51.723 INFO 42417 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2022-12-07 02:56:51.724 INFO 42417 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2022-12-07 02:56:51.724 INFO 42417 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.68] 2022-12-07 02:56:51.733 INFO 42417 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2022-12-07 02:56:51.733 INFO 42417 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 27 ms 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 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 '' 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)
- Make a call
 The service responds$ curl --location --request POST 'http://localhost:8080' \ --header 'Content-Type: application/json' \ --data-raw '{ "body": "{ \"name\": \"CoffeeBeans\" }" }'[ { "name": "CoffeeBeans", "saved": true } ]
Github action
Initial setup for Github action to work
- Create an Identity providers in AWS for github actions.
- Create a new CoffeebeansCoreGithubActionsIam role with the following inline IAM policy
   {
   "Version": "2012-10-17",
   "Statement": [
      {
         "Action": "iam:PassRole",
         "Resource": [
            "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-lookup-role-{aws-account-number}-{aws-region}",
            "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-file-publishing-role-{aws-account-number}-{aws-region}",
            "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-image-publishing-role-{aws-account-number}-{aws-region}",
            "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-cfn-exec-role-{aws-account-number}-{aws-region}",
            "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-deploy-role-{aws-account-number}-{aws-region}"
         ],
         "Effect": "Allow"
      }
   ]
}
and the following trust relationship
{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Effect": "Allow",
         "Principal": {
            "Federated": "arn:aws:iam::{aws-account-number}:oidc-provider/token.actions.githubusercontent.com"
         },
         "Action": "sts:AssumeRoleWithWebIdentity",
         "Condition": {
            "StringEquals": {
               "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
            },
            "StringLike": {
               "token.actions.githubusercontent.com:sub": "repo:{github-account-or-org}/spring-native-aws-lambda:*"
            }
         }
      }
   ]
}
- Create a new CDKBootstrapForCoffeebeansCoreIAM role for CDK bootstrap with the following IAM managed policyCoffeebeansCoreCdkBootstrapAccess
{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Sid": "ECRPermissions",
         "Effect": "Allow",
         "Action": [
            "ecr:CreateRepository",
            "ecr:DeleteRepository",
            "ecr:SetRepositoryPolicy",
            "ecr:DescribeRepositories"
         ],
         "Resource": "arn:aws:ecr:{aws-region}:{aws-account-number}:repository/cdk-{qualifier}-container-assets-{aws-account-number}-{aws-region}"
      },
      {
         "Sid": "IAMPermissions",
         "Effect": "Allow",
         "Action": [
            "iam:GetRole",
            "iam:CreateRole",
            "iam:DeleteRole",
            "iam:AttachRolePolicy",
            "iam:PutRolePolicy",
            "iam:DetachRolePolicy",
            "iam:DeleteRolePolicy"
         ],
         "Resource": [
            "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-lookup-role-{aws-account-number}-{aws-region}",
            "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-file-publishing-role-{aws-account-number}-{aws-region}",
            "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-image-publishing-role-{aws-account-number}-{aws-region}",
            "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-cfn-exec-role-{aws-account-number}-{aws-region}",
            "arn:aws:iam::{aws-account-number}:role/cdk-{qualifier}-deploy-role-{aws-account-number}-{aws-region}"
         ]
      },
      {
         "Sid": "S3Permissions",
         "Effect": "Allow",
         "Action": [
            "s3:PutBucketPublicAccessBlock",
            "s3:CreateBucket",
            "s3:DeleteBucketPolicy",
            "s3:PutEncryptionConfiguration",
            "s3:GetEncryptionConfiguration",
            "s3:PutBucketPolicy",
            "s3:DeleteBucket",
            "s3:PutBucketVersioning"
         ],
         "Resource": [
            "arn:aws:s3:::{qualifier}-cdk-bucket"
         ]
      },
      {
         "Sid": "SSMPermissions",
         "Effect": "Allow",
         "Action": [
            "ssm:DeleteParameter",
            "ssm:AddTagsToResource",
            "ssm:GetParameters",
            "ssm:PutParameter"
         ],
         "Resource": "arn:aws:ssm:{aws-region}:{aws-account-number}:parameter/cdk-bootstrap/{qualifier}/version"
      }
   ]
}
- Create an IAM managed policy CoffeebeansCoreCdkExecutionAccessto be used bycdk-{qualifier}-cfn-exec-role-{aws-account-number}-{aws-region}which is gonna be created by CDK
{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Sid": "S3Permissions",
         "Effect": "Allow",
         "Action": "s3:GetObject",
         "Resource": [
            "arn:aws:s3:::{qualifier}-cdk-bucket",
            "arn:aws:s3:::{qualifier}-cdk-bucket/*"
         ]
      },
      {
         "Sid": "AGWPermissions",
         "Effect": "Allow",
         "Action": [
            "apigateway:POST",
            "apigateway:DELETE",
            "apigateway:GET",
            "apigateway:PATCH",
            "apigateway:PUT"
         ],
         "Resource": [
            "arn:aws:apigateway:{aws-region}::/restapis",
            "arn:aws:apigateway:{aws-region}::/restapis/*",
            "arn:aws:apigateway:{aws-region}::/account"
         ]
      },
      {
         "Sid": "SNSPermissions",
         "Effect": "Allow",
         "Action": [
            "SNS:CreateTopic",
            "SNS:DeleteTopic",
            "SNS:Subscribe",
            "SNS:GetTopicAttributes",
            "SNS:ListSubscriptionsByTopic",
            "SNS:Unsubscribe",
            "SNS:TagResource",
            "SNS:UntagResource"
         ],
         "Resource": [
            "arn:aws:sns:{aws-region}:{aws-account-number}:spring-native-aws-lambda-function-dead-letter-topic"
         ]
      },
      {
         "Sid": "LambdaPermissions",
         "Effect": "Allow",
         "Action": [
            "lambda:GetFunction",
            "lambda:ListFunctions",
            "lambda:DeleteFunction",
            "lambda:CreateFunction",
            "lambda:TagResource",
            "lambda:AddPermission",
            "lambda:RemovePermission",
            "lambda:PutFunctionEventInvokeConfig",
            "lambda:DeleteFunctionEventInvokeConfig",
            "lambda:UpdateFunctionEventInvokeConfig",
            "lambda:UpdateFunctionCode",
            "lambda:ListTags",
            "lambda:UpdateFunctionConfiguration"
         ],
         "Resource": [
            "arn:aws:lambda:{aws-region}:{aws-account-number}:function:spring-native-aws-lambda-function",
            "arn:aws:lambda:{aws-region}:{aws-account-number}:function:spring-native-aws-lambda-function:$LATEST"
         ]
      },
      {
         "Sid": "SSMPermissions",
         "Effect": "Allow",
         "Action": [
            "ssm:GetParameters"
         ],
         "Resource": [
            "arn:aws:ssm:{aws-region}:{aws-account-number}:parameter/cdk-bootstrap/{qualifier}/version"
         ]
      },
      {
         "Sid": "IAMPermissions",
         "Effect": "Allow",
         "Action": [
            "iam:PassRole",
            "iam:GetRole",
            "iam:GetRolePolicy",
            "iam:CreateRole",
            "iam:PutRolePolicy",
            "iam:DeleteRole",
            "iam:DeleteRolePolicy",
            "iam:AttachRolePolicy",
            "iam:DetachRolePolicy"
         ],
         "Resource": [
            "arn:aws:iam::{aws-account-number}:role/spring-native-aws-lambda-springnativeawslambdafun-*",
            "arn:aws:iam::{aws-account-number}:role/spring-native-aws-lambda-springnativeawslambdares-4FVJBBHF9EL2",
            "arn:aws:iam::{aws-account-number}:role/spring-native-aws-lambda-function-rest-api/CloudWatchRole"
         ]
      },
      {
         "Sid": "CFNPermissions",
         "Effect": "Allow",
         "Action": "cloudformation:DescribeStacks",
         "Resource": "arn:aws:cloudformation:{aws-region}:{aws-account-number}:stack/{qualifier}-example-function-dev-stack/*"
      }
   ]
}
- Run the following command to bootstrap the CDK
cdk bootstrap aws://{aws-account-number}/{aws-region} --profile cdk \
  --role-arn arn:aws:iam::{aws-account-number}:role/CDKBootstrapForCoffeebeansCore \
  --cloudformation-execution-policies "arn:aws:iam::{aws-account-number}:policy/CoffeebeansCoreCdkExecutionAccess" \
  --toolkit-stack-name cdk-{qualifier}-toolkit \
  --toolkit-bucket-name {qualifier}-cdk-bucket \
  --qualifier {qualifier} \
  --tags COST_CENTRE=coffeebeans-core
NOTE: notice that the policy passed to --cloudformation-execution-policies option is the one
we created
in step 4
Building AWS Lambda Function from Zip
Now that the setup is done you can deploy to AWS.
- Create a new release in Github releases page, the github action will start and a deployment to AWS environment.
- Test via curl
$ curl --location --request POST 'https://{api-id}.execute-api.ap-southeast-2.amazonaws.com/dev/name' \ --header 'Content-Type: application/json' \ --data-raw '{ "name": "CoffeeBeans" }'
- Et voila! It runs with 500 ms for cold start.