diff --git a/.dockerignore b/.dockerignore
index 4d096dd..fd40775 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -15,5 +15,5 @@ README.md
settings-spring.xml
-spring-native-aws-lambda-infra
+spring-native-aws-service-infra
docker-compose.yml
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7b42b3f..b10cfbd 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -51,6 +51,10 @@ jobs:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
+ - name: maven-settings-xml-action
+ uses: whelk-io/maven-settings-xml-action@v22
+ with:
+ servers: '[{ "id": "github", "password": "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" }]'
- name: Build with Maven
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -58,8 +62,8 @@ jobs:
ENV: ${{ env.ENV }}
COST_CENTRE: ${{ env.COST_CENTRE }}
run: |
- ./mvnw -ntp clean verify -DskipTests -pl spring-native-aws-lambda-infra
- ./mvnw -ntp clean -Pnative -DskipTests native:compile package -pl spring-native-aws-lambda-function
+ ./mvnw -ntp clean verify -DskipTests -pl spring-native-aws-service-infra
+ ./mvnw -ntp clean -Pnative -DskipTests native:compile package -pl spring-native-aws-service
- name: cdk diff
uses: muhamadto/aws-cdk-github-actions@v5
with:
diff --git a/NOTICE b/NOTICE
index c0a3d8a..6cf1727 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,4 +1,4 @@
-Apache [spring-native-aws-lambda-function]
+Apache [spring-native-aws-function]
Copyright 2021 Muhammad Hamadto
This product includes software developed by:
diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
index 71c43c0..0cabf7e 100644
--- a/PULL_REQUEST_TEMPLATE.md
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -7,6 +7,6 @@
-- [ ] Describe what you did in the pull request description
+- [ ] Describe what you did in the pull secret description
- [ ] Add Unit and Integration Tests - at least 80% unit tests for new code.
- [ ] Added or updated documentation
\ No newline at end of file
diff --git a/README.md b/README.md
index dbea69c..3176fa9 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# spring-native-aws-lambda
+# spring-native-aws-function
[![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)
[![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)
@@ -43,12 +43,17 @@ $ ./mvnw -ntp clean verify -U
```
The service responds
```json
- [
- {
- "name": "CoffeeBeans",
- "saved": true
- }
- ]
+ {
+ "id": "production1234someapp",
+ "env": "production",
+ "costCentre": "1234",
+ "applicationName": "some-app",
+ "items": {
+ "GITHUB_TOKEN": "WOAH",
+ "AWS_ACCESS_KEY_ID": "OMG",
+ "AWS_SECRET_ACCESS_KEY": "OH NO"
+ }
+ }
```
#### Using `mvnw`
@@ -56,8 +61,8 @@ $ ./mvnw -ntp clean verify -U
1. Run the following commands
```shell
$ 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-function
+ $ ./mvnw -ntp clean -DskipTests -Pnative native:compile package -pl spring-native-aws-service
+ $ ./spring-native-aws-service/target/spring-native-aws-service
```
The service starts in less than 100 ms
```shell
@@ -77,17 +82,24 @@ $ ./mvnw -ntp clean verify -U
$ curl --location --request POST 'http://localhost:8080' \
--header 'Content-Type: application/json' \
--data-raw '{
- "body": "{ \"name\": \"CoffeeBeans\" }"
+ "httpMethod": "POST",
+ "body": "{ \"env\": \"production\", \"costCentre\": \"1234\", \"applicationName\": \"some-app\", \"items\": { \"GITHUB_TOKEN\": \"WOAH\", \"AWS_ACCESS_KEY_ID\": \"OMG\", \"AWS_SECRET_ACCESS_KEY\": \"OH NO\" } }"
}'
```
+
The service responds
```json
- [
- {
- "name": "CoffeeBeans",
- "saved": true
- }
- ]
+ {
+ "id": "production1234someapp",
+ "env": "production",
+ "costCentre": "1234",
+ "applicationName": "some-app",
+ "items": {
+ "GITHUB_TOKEN": "WOAH",
+ "AWS_ACCESS_KEY_ID": "OMG",
+ "AWS_SECRET_ACCESS_KEY": "OH NO"
+ }
+ }
```
### Github action
@@ -134,7 +146,7 @@ and the following trust relationship
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
- "token.actions.githubusercontent.com:sub": "repo:{github-account-or-org}/spring-native-aws-lambda:*"
+ "token.actions.githubusercontent.com:sub": "repo:{github-account-or-org}/spring-native-aws-service:*"
}
}
}
@@ -150,67 +162,151 @@ and the following trust relationship
"Version": "2012-10-17",
"Statement": [
{
- "Sid": "ECRPermissions",
+ "Sid": "S3Permissions",
+ "Effect": "Allow",
+ "Action": "s3:GetObject",
+ "Resource": [
+ "arn:aws:s3:::cdk-cbcore-assets-718055627712-ap-southeast-2",
+ "arn:aws:s3:::cdk-cbcore-assets-718055627712-ap-southeast-2/*"
+ ]
+ },
+ {
+ "Sid": "AGWPermissions",
"Effect": "Allow",
"Action": [
- "ecr:CreateRepository",
- "ecr:DeleteRepository",
- "ecr:SetRepositoryPolicy",
- "ecr:DescribeRepositories"
+ "apigateway:POST",
+ "apigateway:DELETE",
+ "apigateway:GET",
+ "apigateway:PATCH",
+ "apigateway:PUT"
],
- "Resource": "arn:aws:ecr:{aws-region}:{aws-account-number}:repository/cdk-{qualifier}-container-assets-{aws-account-number}-{aws-region}"
+ "Resource": [
+ "arn:aws:apigateway:ap-southeast-2::/restapis",
+ "arn:aws:apigateway:ap-southeast-2::/restapis/*",
+ "arn:aws:apigateway:ap-southeast-2::/account",
+ "arn:aws:apigateway:ap-southeast-2::/tags/arn:aws:apigateway:ap-southeast-2::/restapis/*"
+ ]
},
{
- "Sid": "IAMPermissions",
+ "Sid": "SNSPermissions",
"Effect": "Allow",
"Action": [
- "iam:GetRole",
- "iam:CreateRole",
- "iam:DeleteRole",
- "iam:AttachRolePolicy",
- "iam:PutRolePolicy",
- "iam:DetachRolePolicy",
- "iam:DeleteRolePolicy"
+ "SNS:CreateTopic",
+ "SNS:DeleteTopic",
+ "SNS:Subscribe",
+ "SNS:GetTopicAttributes",
+ "SNS:ListSubscriptionsByTopic",
+ "SNS:Unsubscribe",
+ "SNS:TagResource",
+ "SNS:UntagResource"
],
"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}"
+ "arn:aws:sqs:ap-southeast-2:718055627712:SpringNativeAwsFunctionStack-LambdaDeadLetterTopic*"
]
},
{
- "Sid": "S3Permissions",
+ "Sid": "SQSPermissions",
"Effect": "Allow",
"Action": [
- "s3:PutBucketPublicAccessBlock",
- "s3:CreateBucket",
- "s3:DeleteBucketPolicy",
- "s3:PutEncryptionConfiguration",
- "s3:GetEncryptionConfiguration",
- "s3:PutBucketPolicy",
- "s3:DeleteBucket",
- "s3:PutBucketVersioning"
+ "sqs:GetQueueAttributes",
+ "sqs:CreateQueue",
+ "sqs:DeleteQueue",
+ "sqs:GetQueueUrl",
+ "sqs:SetQueueAttributes",
+ "sqs:ListQueues"
],
"Resource": [
- "arn:aws:s3:::{qualifier}-cdk-bucket"
+ "arn:aws:sqs:ap-southeast-2:718055627712:SpringNativeAwsFunctionStack-LambdaDeadLetterQueue*"
+ ]
+ },
+ {
+ "Sid": "LambdaPermissions",
+ "Effect": "Allow",
+ "Action": [
+ "lambda:GetFunction",
+ "lambda:ListFunctions",
+ "lambda:DeleteFunction",
+ "lambda:CreateFunction",
+ "lambda:TagResource",
+ "lambda:AddPermission",
+ "lambda:RemovePermission",
+ "lambda:PutFunctionEventInvokeConfig",
+ "lambda:UpdateFunctionEventInvokeConfig",
+ "lambda:DeleteFunctionEventInvokeConfig",
+ "lambda:UpdateFunctionCode",
+ "lambda:ListTags",
+ "lambda:UpdateFunctionConfiguration"
+ ],
+ "Resource": [
+ "arn:aws:lambda:ap-southeast-2:718055627712:function:SpringNativeAwsFunctionStack*"
]
},
{
"Sid": "SSMPermissions",
"Effect": "Allow",
"Action": [
- "ssm:DeleteParameter",
- "ssm:AddTagsToResource",
- "ssm:GetParameters",
- "ssm:PutParameter"
+ "ssm:GetParameters"
+ ],
+ "Resource": [
+ "arn:aws:ssm:ap-southeast-2:718055627712:parameter/cdk-bootstrap/cbcore/version"
+ ]
+ },
+ {
+ "Sid": "DynamoDBPermissions",
+ "Effect": "Allow",
+ "Action": [
+ "dynamodb:DescribeTable",
+ "dynamodb:CreateTable",
+ "dynamodb:DeleteTable",
+ "dynamodb:TagResource",
+ "dynamodb:UntagResource",
+ "dynamodb:ListTagsOfResource",
+ "dynamodb:DescribeTimeToLive",
+ "dynamodb:DescribeContributorInsights",
+ "dynamodb:DescribeContinuousBackups",
+ "dynamodb:DescribeKinesisStreamingDestination"
+ ],
+ "Resource": [
+ "arn:aws:dynamodb:ap-southeast-2:718055627712:table/secrets",
+ "arn:aws:dynamodb:ap-southeast-2:718055627712:table/SpringNativeAwsFunction*"
+ ]
+ },
+ {
+ "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::718055627712:role/SpringNativeAwsFunction*"
+ ]
+ },
+ {
+ "Sid": "CFNPermissions",
+ "Effect": "Allow",
+ "Action": "cloudformation:DescribeStacks",
+ "Resource": "arn:aws:cloudformation:ap-southeast-2:718055627712:stack/cbcore-example-function-dev-stack/*"
+ },
+ {
+ "Sid": "ApplicationAutoscalingPermissions",
+ "Effect": "Allow",
+ "Action": [
+ "application-autoscaling:DeregisterScalableTarget"
],
- "Resource": "arn:aws:ssm:{aws-region}:{aws-account-number}:parameter/cdk-bootstrap/{qualifier}/version"
+ "Resource": [
+ "arn:aws:application-autoscaling:ap-southeast-2:718055627712:scalable-target/*"
+ ]
}
]
-}
-```
+}```
4. Create an IAM managed policy `CoffeebeansCoreCdkExecutionAccess` to be used
by `cdk-{qualifier}-cfn-exec-role-{aws-account-number}-{aws-region}` which is gonna be created by
@@ -259,7 +355,7 @@ and the following trust relationship
"SNS:UntagResource"
],
"Resource": [
- "arn:aws:sns:{aws-region}:{aws-account-number}:spring-native-aws-lambda-function-dead-letter-topic"
+ "arn:aws:sns:{aws-region}:{aws-account-number}:spring-native-aws-function-dead-letter-topic"
]
},
{
@@ -281,8 +377,8 @@ and the following trust relationship
"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"
+ "arn:aws:lambda:{aws-region}:{aws-account-number}:function:spring-native-aws-function",
+ "arn:aws:lambda:{aws-region}:{aws-account-number}:function:spring-native-aws-function:$LATEST"
]
},
{
@@ -310,9 +406,9 @@ and the following trust relationship
"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"
+ "arn:aws:iam::{aws-account-number}:role/spring-native-aws-service-springnativeawslambdafun-*",
+ "arn:aws:iam::{aws-account-number}:role/spring-native-aws-service-springnativeawslambdares-4FVJBBHF9EL2",
+ "arn:aws:iam::{aws-account-number}:role/spring-native-aws-function-rest-api/CloudWatchRole"
]
},
{
@@ -346,15 +442,32 @@ in step 4
Now that the setup is done you can deploy to AWS.
1. Create a new release in
- Github [releases page](https://github.com/muhamadto/spring-native-aws-lambda/releases),
+ Github [releases page](https://github.com/muhamadto/spring-native-aws-service/releases),
the [github action](.github/workflows/release.yml) will start and a deployment to AWS
environment.
2. Test via curl
- ```shell
- $ 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"
+
+POST
+
+```shell
+ $ curl --location --request POST 'https://{apiid}.execute-api.ap-southeast-2.amazonaws.com/dev/' \
+ --header 'Content-Type: application/json' \
+ --data-raw '{
+ "env": "production",
+ "costCentre": "1234",
+ "applicationName": "some-app",
+ "items": {
+ "GITHUB_TOKEN": "WOAH",
+ "AWS_ACCESS_KEY_ID": "OMG",
+ "AWS_SECRET_ACCESS_KEY": "OH NO"
+ }
}'
- ```
-3. Et voila! It runs with 500 ms for cold start.
\ No newline at end of file
+```
+
+GET
+
+ ```shell
+ curl --request GET 'https://{apiid}.execute-api.ap-southeast-2.amazonaws.com/dev/production-1234-someapp'
+ ```
+3. Et voila! It runs with 500 ms for cold start.
+
diff --git a/cdk.json b/cdk.json
index 350653f..04ac302 100644
--- a/cdk.json
+++ b/cdk.json
@@ -1,3 +1,6 @@
{
- "app": "./mvnw exec:java -pl spring-native-aws-lambda-infra -Dexec.mainClass=com.coffeebeans.springnativeawslambda.infra.Application"
+ "app": "./mvnw exec:java -pl spring-native-aws-service-infra -Dexec.mainClass=com.coffeebeans.springnativeawslambda.infra.Application",
+ "context": {
+ "@aws-cdk/core:bootstrapQualifier": "cbcore"
+ }
}
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index da2dd19..696ff14 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,8 +15,8 @@
version: "3.9"
services:
- spring-native-aws-lambda-function-infra:
- image: muhamadto/spring-native-amazonlinux2-builder:21-amazonlinux2-awscliv2
+ spring-native-aws-function-infra:
+ image: ghcr.io/muhamadto/spring-native-amazonlinux2-builder:21-amazonlinux2-awscliv2
volumes:
- ./:/app
- ${M2_REPO}:/home/worker/.m2
@@ -29,7 +29,9 @@ services:
AWS_SECRET_ACCESS_KEY: local
AWS_ENDPOINT_URL: http://localstack:4566
BUILD_ARTIFACT: 'true'
- FUNCTION_NAME: spring-native-aws-lambda-function
+ FUNCTION_NAME: spring-native-aws-service
+ SPRING_MAIN_WEBAPPLICATIONTYPE: servlet
+ SPRING_CLOULD_AWS_DYNAMODB_ENDPOINT: http://localstack:4566
STAGE: compose
MAVEN_OPTS: |
-DskipTests=true
@@ -45,7 +47,7 @@ services:
- -c
command: |
'
- function package_spring_native_lambda() {
+ function package_spring_native_function() {
if [ "$$BUILD_ARTIFACT" = "true" ]; then
./mvnw -ntp clean -Pnative -DskipTests native:compile package -pl "$$FUNCTION_NAME"
else
@@ -61,7 +63,7 @@ services:
print_info_message "divider" "Package GraalVM function" &&
- package_spring_native_lambda &&
+ package_spring_native_function &&
print_info_message "divider" "Creating LAMBDA function" &&
lambda_create_function "$$FUNCTION_NAME" provided.al2023 512 ./"$$FUNCTION_NAME"/target/"$$FUNCTION_NAME"-native-zip.zip "$$FUNCTION_NAME" &&
diff --git a/pom.xml b/pom.xml
index 9c355de..6a831cd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,7 @@
org.springframework.boot
spring-boot-starter-parent
3.2.1
-
+
com.coffeebeans
@@ -34,8 +34,7 @@
${project.artifactId}
${revision}
pom
- Demo project for Spring cloud function with graalvm native image deployed with cdk
-
+ Demo project for Spring cloud function with graalvm native image deployed with cdk
@@ -79,26 +78,42 @@
- spring-native-aws-lambda-function
- spring-native-aws-lambda-infra
+ spring-native-aws-service
+ spring-native-aws-service-infra
+
+ io.awspring.cloud
+ spring-cloud-aws-dependencies
+ 3.1.1
+ pom
+ import
+
+
com.coffeebeans
- spring-native-aws-lambda-function
+ spring-native-aws-service
${project.version}
com.coffeebeans
- spring-native-aws-lambda-infra
+ spring-native-aws-service-infra
${project.version}
+
+ io.sandpipers
+ sandpipers-cdk-bom
+ 1.0-SNAPSHOT
+ pom
+ import
+
+
com.amazonaws
@@ -187,7 +202,7 @@
software.amazon.awscdk
aws-cdk-lib
- 2.116.1
+ 2.128.0
@@ -213,6 +228,13 @@
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit-jupiter.version}
+ test
+
+
org.mockito
mockito-core
diff --git a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/function/ExampleFunction.java b/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/function/ExampleFunction.java
deleted file mode 100644
index b24fb38..0000000
--- a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/function/ExampleFunction.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to Muhammad Hamadto
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.coffeebeans.springnativeawslambda.function;
-
-import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
-import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
-import com.coffeebeans.springnativeawslambda.model.Request;
-import com.coffeebeans.springnativeawslambda.model.Response;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import jakarta.validation.constraints.NotNull;
-import java.util.function.Function;
-import lombok.SneakyThrows;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-import org.springframework.validation.annotation.Validated;
-
-@Component
-@Slf4j
-@Validated
-public class ExampleFunction implements
- Function {
-
- private final ObjectMapper objectMapper;
-
- public ExampleFunction(@NotNull final ObjectMapper objectMapper) {
- this.objectMapper = objectMapper;
- }
-
- /**
- * Lambda function handler that takes a request and returns a response.
- *
- * @param proxyRequestEvent the function argument
- * @return {@link APIGatewayProxyResponseEvent}
- * @throws JsonProcessingException
- */
- @Override
- @SneakyThrows(value = JsonProcessingException.class)
- public APIGatewayProxyResponseEvent apply(final APIGatewayProxyRequestEvent proxyRequestEvent) {
- log.info("Converting request into a response...'");
-
- final Request request = objectMapper.readValue(proxyRequestEvent.getBody(), Request.class);
-
- final Response response = Response.builder()
- .name(request.getName())
- .saved(true)
- .build();
-
- log.info("Converted request into a response.");
-
- return new APIGatewayProxyResponseEvent()
- .withStatusCode(200)
- .withBody(objectMapper.writeValueAsString(response));
- }
-}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-function/src/test/java/com/coffeebeans/springnativeawslambda/function/ExampleFunctionTest.java b/spring-native-aws-lambda-function/src/test/java/com/coffeebeans/springnativeawslambda/function/ExampleFunctionTest.java
deleted file mode 100644
index 801f406..0000000
--- a/spring-native-aws-lambda-function/src/test/java/com/coffeebeans/springnativeawslambda/function/ExampleFunctionTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to Muhammad Hamadto
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.coffeebeans.springnativeawslambda.function;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
-import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
-import com.coffeebeans.springnativeawslambda.function.ExampleFunction;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.Spy;
-
-class ExampleFunctionTest {
-
- private ExampleFunction exampleFunction;
-
- @Spy
- private ObjectMapper objectMapper = new ObjectMapper();
-
- @BeforeEach
- void setUp() {
- exampleFunction = new ExampleFunction(objectMapper);
- }
-
- @Test
- void should_return_APIGatewayProxyResponseEvent() {
- final String requestBody = "{\"name\":\"Coffeebeans\"}";
- final String responseBody = "{\"name\":\"Coffeebeans\",\"saved\":true}";
-
- final APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent()
- .withBody(requestBody);
-
- final APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent = new APIGatewayProxyResponseEvent()
- .withStatusCode(200)
- .withBody(responseBody);
-
- final APIGatewayProxyResponseEvent actual = exampleFunction.apply(request);
-
- assertThat(actual)
- .isEqualTo(apiGatewayProxyResponseEvent);
- }
-
- @Test
- void should_throw_JsonProcessingException() {
-
- final APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent()
- .withBody("Coffeebeans");
-
- assertThatThrownBy(() -> exampleFunction.apply(request))
- .isInstanceOf(JsonProcessingException.class);
- }
-}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/ApiBaseStack.java b/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/ApiBaseStack.java
deleted file mode 100644
index fafc682..0000000
--- a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/ApiBaseStack.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Licensed to Muhammad Hamadto
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.coffeebeans.springnativeawslambda.infra;
-
-import com.coffeebeans.springnativeawslambda.infra.lambda.CustomRuntime2023Function;
-import org.apache.commons.lang3.StringUtils;
-import software.amazon.awscdk.Duration;
-import software.amazon.awscdk.Stack;
-import software.amazon.awscdk.StackProps;
-import software.amazon.awscdk.services.apigateway.LambdaRestApi;
-import software.amazon.awscdk.services.apigateway.Resource;
-import software.amazon.awscdk.services.apigateway.StageOptions;
-import software.amazon.awscdk.services.ec2.IVpc;
-import software.amazon.awscdk.services.iam.IRole;
-import software.amazon.awscdk.services.iam.Role;
-import software.amazon.awscdk.services.lambda.Code;
-import software.amazon.awscdk.services.lambda.Function;
-import software.amazon.awscdk.services.lambda.FunctionProps;
-import software.amazon.awscdk.services.sns.ITopic;
-import software.amazon.awscdk.services.sns.Topic;
-import software.amazon.awscdk.services.sns.subscriptions.SqsSubscription;
-import software.amazon.awscdk.services.sqs.DeadLetterQueue;
-import software.amazon.awscdk.services.sqs.DeduplicationScope;
-import software.amazon.awscdk.services.sqs.Queue;
-import software.constructs.Construct;
-
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-import java.util.Map;
-
-import static software.amazon.awscdk.services.lambda.Runtime.PROVIDED_AL2023;
-
-public class ApiBaseStack extends Stack {
-
- private static final int LAMBDA_FUNCTION_TIMEOUT_IN_SECONDS = 3;
- private static final int LAMBDA_FUNCTION_MEMORY_SIZE = 512;
- private static final int LAMBDA_FUNCTION_RETRY_ATTEMPTS = 2;
- private static final String FIFO_SUFFIX = ".fifo";
- private static final String DEAD_LETTER_QUEUE_SUFFIX = "-dlq";
-
- public ApiBaseStack(
- @NotNull final Construct scope,
- @NotBlank final String id,
- @NotNull final StackProps props) {
- super(scope, id, props);
- }
-
- @NotNull
- protected Queue createQueue(@NotBlank final String queueId) {
- final DeadLetterQueue deadLetterQueue = createDeadLetterQueue(
- queueId + DEAD_LETTER_QUEUE_SUFFIX);
-
- return Queue.Builder.create(this, queueId)
- .queueName(queueId)
- .deadLetterQueue(deadLetterQueue)
- .build();
- }
-
- @NotNull
- protected Queue createFifoQueue(
- @NotBlank final String queueId,
- final boolean contentBasedDeduplication,
- @NotNull final DeduplicationScope messageGroup) {
- final String fifoQueueId = queueId + FIFO_SUFFIX;
- final String fifoDeadLetterQueueId = queueId + DEAD_LETTER_QUEUE_SUFFIX;
-
- final DeadLetterQueue deadLetterQueue =
- createFifoDeadLetterQueue(fifoDeadLetterQueueId, contentBasedDeduplication, messageGroup);
-
- return Queue.Builder.create(this, fifoQueueId)
- .queueName(fifoQueueId)
- .fifo(true)
- .deadLetterQueue(deadLetterQueue)
- .contentBasedDeduplication(contentBasedDeduplication)
- .deduplicationScope(messageGroup)
- .build();
- }
-
- @NotNull
- protected DeadLetterQueue createDeadLetterQueue(@NotBlank final String deadLetterQueueId) {
- final Queue queue = Queue.Builder.create(this, deadLetterQueueId)
- .queueName(deadLetterQueueId)
- .build();
-
- return DeadLetterQueue.builder()
- .queue(queue)
- .maxReceiveCount(3)
- .build();
- }
-
- @NotNull
- protected DeadLetterQueue createFifoDeadLetterQueue(
- @NotBlank final String deadLetterQueueId,
- final boolean contentBasedDeduplication,
- @NotNull final DeduplicationScope messageGroup) {
- final String fifoDeadLetterQueueId = deadLetterQueueId + FIFO_SUFFIX;
- final Queue queue = Queue.Builder.create(this, fifoDeadLetterQueueId)
- .queueName(fifoDeadLetterQueueId)
- .fifo(true)
- .contentBasedDeduplication(contentBasedDeduplication)
- .deduplicationScope(messageGroup)
- .build();
-
- return DeadLetterQueue.builder()
- .queue(queue)
- .maxReceiveCount(3)
- .build();
- }
-
- @NotNull
- protected Topic createTopic(
- @NotBlank final String topicId) {
-
- return Topic.Builder.create(this, topicId)
- .topicName(topicId)
- .build();
- }
-
- @NotNull
- protected Topic createFifoTopic(
- @NotBlank final String topicId,
- final boolean fifo,
- final boolean contentBasedDeduplication) {
- String fifoTopicId = topicId + FIFO_SUFFIX;
-
- return Topic.Builder.create(this, fifoTopicId)
- .topicName(fifoTopicId)
- .fifo(fifo)
- .contentBasedDeduplication(contentBasedDeduplication)
- .build();
- }
-
- @NotNull
- protected static SqsSubscription createSqsSubscription(@NotNull final Queue queue) {
- return SqsSubscription.Builder.create(queue).build();
- }
-
- @NotNull
- protected Function createFunction(
- @NotBlank final String lambdaId,
- @NotBlank final String handler,
- @NotNull final Code code,
- @NotNull final Topic deadLetterTopic,
- @NotNull Role role,
- @NotEmpty final Map environment) {
- return this.createFunction(null,
- lambdaId,
- handler,
- code,
- deadLetterTopic,
- role,
- environment);
- }
-
- @NotNull
- protected Function createFunction(
- final IVpc vpc,
- @NotBlank final String lambdaId,
- @NotBlank final String handler,
- @NotNull final Code code,
- @NotNull final ITopic deadLetterTopic,
- @NotNull IRole role,
- @NotEmpty final Map environment) {
-
- FunctionProps functionProps = FunctionProps.builder()
- .runtime(PROVIDED_AL2023)
- .functionName(lambdaId)
- .description("Lambda example with spring native")
- .code(code)
- .handler(handler)
- .role(role)
- .vpc(vpc)
- .environment(environment)
- .deadLetterTopic(deadLetterTopic)
- .timeout(Duration.seconds(LAMBDA_FUNCTION_TIMEOUT_IN_SECONDS))
- .memorySize(LAMBDA_FUNCTION_MEMORY_SIZE)
- .retryAttempts(LAMBDA_FUNCTION_RETRY_ATTEMPTS)
- .build();
-
- return new CustomRuntime2023Function(this, lambdaId, functionProps)
- .getFunction();
-
- }
-
- @NotNull
- protected LambdaRestApi createLambdaRestApi(
- @NotBlank final String stageName,
- @NotBlank final String restApiId,
- @NotNull final String resourceName,
- @NotNull final String httpMethod,
- @NotNull final Function function,
- final boolean proxy) {
-
- // point to the lambda
- final LambdaRestApi lambdaRestApi = LambdaRestApi.Builder.create(this, restApiId)
- .restApiName(restApiId)
- .handler(function)
- .proxy(proxy)
- .deployOptions(StageOptions.builder().stageName(stageName).build())
- .build();
-
- // get root resource to add methods
- final Resource resource = lambdaRestApi.getRoot().addResource(resourceName);
- resource.addMethod(StringUtils.toRootUpperCase(httpMethod));
-
- return lambdaRestApi;
- }
-}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Application.java b/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Application.java
deleted file mode 100644
index 15ecaaf..0000000
--- a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Application.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to Muhammad Hamadto
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.coffeebeans.springnativeawslambda.infra;
-
-import lombok.NoArgsConstructor;
-import software.amazon.awscdk.App;
-import software.amazon.awscdk.Tags;
-
-import java.util.Map;
-import java.util.Objects;
-
-import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE;
-import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_ENV;
-import static com.coffeebeans.springnativeawslambda.infra.StackUtils.createStack;
-import static com.coffeebeans.springnativeawslambda.infra.TagUtils.createTags;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static lombok.AccessLevel.PRIVATE;
-
-@NoArgsConstructor(access = PRIVATE)
-public final class Application {
-
- private static final String DEV_STACK_NAME = "spring-native-aws-lambda-function-dev-stack";
- private static final String PRD_STACK_NAME = "spring-native-aws-lambda-function-prd-stack";
- private static final String ENVIRONMENT_NAME_DEV = "dev";
- private static final String ENVIRONMENT_NAME_PRD = "prd";
- private static final String LAMBDA_CODE_PATH =
- SpringNativeAwsLambdaStack.LAMBDA_FUNCTION_ID
- + "/target/spring-native-aws-lambda-function-native-zip.zip";
- private static final String QUALIFIER = "cbcore";
- private static final String FILE_ASSETS_BUCKET_NAME = "cbcore-cdk-bucket";
-
- public static void main(final String... args) {
- final App app = new App();
-
- final String env = System.getenv(KEY_ENV);
- checkNotNull(env, "'env' environment variable is required");
- final Map tags = createTags(env, KEY_COST_CENTRE);
-
- switch (env) {
- case ENVIRONMENT_NAME_DEV ->
- createStack(app, DEV_STACK_NAME, LAMBDA_CODE_PATH, QUALIFIER, FILE_ASSETS_BUCKET_NAME, env);
- case ENVIRONMENT_NAME_PRD ->
- createStack(app, PRD_STACK_NAME, LAMBDA_CODE_PATH, QUALIFIER, FILE_ASSETS_BUCKET_NAME, env);
- default -> throw new IllegalArgumentException("Environment variable " + KEY_ENV
- + " is not set to a valid value. Set it to '[dev|prd]'");
- }
-
- tags.entrySet().stream()
- .filter(tag -> Objects.nonNull(tag.getValue()))
- .forEach(tag -> Tags.of(app).add(tag.getKey(), tag.getValue()));
-
- app.synth();
- }
-}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/CoffeeBeansConstruct.java b/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/CoffeeBeansConstruct.java
deleted file mode 100644
index 141706d..0000000
--- a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/CoffeeBeansConstruct.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.coffeebeans.springnativeawslambda.infra;
-
-import software.constructs.IConstruct;
-
-public interface CoffeeBeansConstruct extends IConstruct {
-
-
-}
diff --git a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/SpringNativeAwsLambdaStack.java b/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/SpringNativeAwsLambdaStack.java
deleted file mode 100644
index c850914..0000000
--- a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/SpringNativeAwsLambdaStack.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Licensed to Muhammad Hamadto
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.coffeebeans.springnativeawslambda.infra;
-
-import software.amazon.awscdk.StackProps;
-import software.amazon.awscdk.services.iam.IManagedPolicy;
-import software.amazon.awscdk.services.iam.Role;
-import software.amazon.awscdk.services.iam.ServicePrincipal;
-import software.amazon.awscdk.services.lambda.AssetCode;
-import software.amazon.awscdk.services.lambda.Function;
-import software.amazon.awscdk.services.sns.Topic;
-import software.constructs.Construct;
-
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotNull;
-import java.util.List;
-import java.util.Map;
-
-import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_ENV;
-import static software.amazon.awscdk.services.iam.ManagedPolicy.fromAwsManagedPolicyName;
-import static software.amazon.awscdk.services.lambda.Code.fromAsset;
-
-public class SpringNativeAwsLambdaStack extends ApiBaseStack {
-
- static final String LAMBDA_FUNCTION_ID = "spring-native-aws-lambda-function";
- private static final String REST_API_ID = LAMBDA_FUNCTION_ID + "-rest-api";
- private static final String DEAD_LETTER_TOPIC_ID = LAMBDA_FUNCTION_ID + "-dead-letter-topic";
- private static final String LAMBDA_HANDLER = "org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest";
- private static final String ENVIRONMENT_VARIABLE_SPRING_PROFILES_ACTIVE = "SPRING_PROFILES_ACTIVE";
-
- public SpringNativeAwsLambdaStack(
- @NotNull final Construct scope,
- @NotBlank final String id,
- @NotBlank final String lambdaCodePath,
- @NotBlank final String stage,
- @NotNull final StackProps props) {
-
- super(scope, id, props);
-
- final Topic deadLetterTopic = createTopic(DEAD_LETTER_TOPIC_ID);
-
- final List managedPolicies =
- List.of(fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"));
-
- final Role role = Role.Builder.create(this, LAMBDA_FUNCTION_ID + "-role")
- .assumedBy(new ServicePrincipal("lambda.amazonaws.com"))
- .managedPolicies(managedPolicies)
- .build();
-
- final AssetCode assetCode = fromAsset(lambdaCodePath);
-
- final Map environment =
- Map.of(ENVIRONMENT_VARIABLE_SPRING_PROFILES_ACTIVE, stage, KEY_ENV, stage);
-
- final Function function = createFunction(
- LAMBDA_FUNCTION_ID,
- LAMBDA_HANDLER,
- assetCode,
- deadLetterTopic,
- role,
- environment);
-
- createLambdaRestApi(stage, REST_API_ID, "name", "POST", function, true);
- }
-}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/StackUtils.java b/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/StackUtils.java
deleted file mode 100644
index d29f592..0000000
--- a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/StackUtils.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Licensed to Muhammad Hamadto
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.coffeebeans.springnativeawslambda.infra;
-
-import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.NotEmpty;
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-import org.jetbrains.annotations.NotNull;
-import software.amazon.awscdk.App;
-import software.amazon.awscdk.DefaultStackSynthesizer;
-import software.amazon.awscdk.StackProps;
-
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public final class StackUtils {
-
- @NotNull
- public static SpringNativeAwsLambdaStack createStack(
- @NotNull final App app,
- @NotBlank final String stackName,
- @NotBlank final String lambdaCodePath,
- @NotBlank final String qualifier,
- @NotBlank final String fileAssetsBucketName,
- @NotEmpty final String stage) {
- final StackProps stackProps = createStackProps(stackName, qualifier, fileAssetsBucketName);
- return new SpringNativeAwsLambdaStack(app, stackName, lambdaCodePath, stage, stackProps);
- }
-
- @NotNull
- private static StackProps createStackProps(@NotBlank final String stackName,
- @NotBlank final String qualifier,
- @NotBlank final String fileAssetsBucketName) {
- return StackProps.builder()
- .synthesizer(createDefaultStackSynthesizer(qualifier, fileAssetsBucketName))
- .stackName(stackName)
- .build();
- }
-
- @NotNull
- private static DefaultStackSynthesizer createDefaultStackSynthesizer(
- @NotBlank final String qualifier,
- @NotBlank final String fileAssetsBucketName) {
- return DefaultStackSynthesizer.Builder.create()
- .qualifier(qualifier)
- .fileAssetsBucketName(fileAssetsBucketName)
- .build();
- }
-}
diff --git a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/lambda/CustomRuntime2023Function.java b/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/lambda/CustomRuntime2023Function.java
deleted file mode 100644
index 99e35de..0000000
--- a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/lambda/CustomRuntime2023Function.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Licensed to Muhammad Hamadto
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.coffeebeans.springnativeawslambda.infra.lambda;
-
-import com.coffeebeans.springnativeawslambda.infra.CoffeeBeansConstruct;
-import lombok.Getter;
-import org.jetbrains.annotations.NotNull;
-import software.amazon.awscdk.Duration;
-import software.amazon.awscdk.services.lambda.Function;
-import software.amazon.awscdk.services.lambda.FunctionProps;
-import software.constructs.Construct;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static org.apache.commons.collections4.MapUtils.isNotEmpty;
-import static org.apache.commons.lang3.StringUtils.isNoneBlank;
-import static software.amazon.awscdk.services.lambda.Runtime.PROVIDED_AL2023;
-
-/**
- * A lambda function with runtime provided.al2023 (custom runtime al2023). Creating function with any other
- * runtime (e.g. passed in the {@link FunctionProps} or via any other means will be ignored).
- */
-
-@Getter
-public class CustomRuntime2023Function extends Construct implements CoffeeBeansConstruct {
-
- private static final int FUNCTION_DEFAULT_TIMEOUT_IN_SECONDS = 10;
- private static final int FUNCTION_DEFAULT_MEMORY_SIZE = 512;
- private static final int FUNCTION_DEFAULT_RETRY_ATTEMPTS = 2;
-
- private final software.amazon.awscdk.services.lambda.Function function;
-
-
- /**
- * @param scope This parameter is required.
- * @param id This parameter is required.
- * @param props This parameter is required.
- */
- public CustomRuntime2023Function(@NotNull final Construct scope,
- @NotNull final String id,
- @NotNull final FunctionProps props) {
- super(scope, id);
-
- checkArgument(isNoneBlank(props.getHandler()), "'handler' is required");
- checkArgument(isNoneBlank(props.getDescription()), "'description' is required");
- checkArgument(isNotEmpty(props.getEnvironment()), "'environment' is required");
-
- final Duration timeout = props.getTimeout() == null
- ? Duration.seconds(FUNCTION_DEFAULT_TIMEOUT_IN_SECONDS)
- : props.getTimeout();
-
- final Number memorySize = props.getMemorySize() == null
- ? FUNCTION_DEFAULT_MEMORY_SIZE
- : props.getMemorySize();
-
- final Number retryAttempts = props.getRetryAttempts() == null
- ? FUNCTION_DEFAULT_RETRY_ATTEMPTS
- : props.getRetryAttempts();
-
- checkArgument(props.getMemorySize().intValue() >= 128
- && props.getMemorySize().intValue() <= 3008,
- "'memorySize' must be between 128 and 3008 (inclusive)");
-
- checkArgument(props.getRetryAttempts().intValue() >= 0
- && props.getRetryAttempts().intValue() <= 2,
- "'retryAttempts' must be between 0 and 2 (inclusive)");
-
-
- function = Function.Builder.create(this, id)
- .runtime(PROVIDED_AL2023)
- .functionName(props.getFunctionName())
- .description(props.getDescription())
- .code(props.getCode())
- .handler(props.getHandler())
- .role(props.getRole())
- .vpc(props.getVpc())
- .environment(props.getEnvironment())
- .deadLetterTopic(props.getDeadLetterTopic())
- .timeout(timeout)
- .memorySize(memorySize)
- .retryAttempts(retryAttempts)
- .build();
-
- }
-}
diff --git a/spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/ApiBaseStackTest.java b/spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/ApiBaseStackTest.java
deleted file mode 100644
index 8b114d5..0000000
--- a/spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/ApiBaseStackTest.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Licensed to Muhammad Hamadto
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.coffeebeans.springnativeawslambda.infra;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static software.amazon.awscdk.services.ec2.Vpc.Builder.create;
-import static software.amazon.awscdk.services.iam.Role.fromRoleArn;
-import static software.amazon.awscdk.services.lambda.Runtime.PROVIDED_AL2023;
-import static software.amazon.awscdk.services.sns.Topic.fromTopicArn;
-import static software.amazon.awscdk.services.sqs.DeduplicationScope.MESSAGE_GROUP;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Map;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-import software.amazon.awscdk.App;
-import software.amazon.awscdk.services.apigateway.LambdaRestApi;
-import software.amazon.awscdk.services.ec2.Vpc;
-import software.amazon.awscdk.services.lambda.Code;
-import software.amazon.awscdk.services.lambda.Function;
-import software.amazon.awscdk.services.sns.Topic;
-import software.amazon.awscdk.services.sqs.DeadLetterQueue;
-import software.amazon.awscdk.services.sqs.Queue;
-
-class ApiBaseStackTest {
-
- private static final String ENV = "test";
-
- private ApiBaseStack apiBaseStack;
-
- private final App app = new App();
-
- private Path lambdaCodePath;
-
- @TempDir
- private static Path TEMP_DIR;
-
- @BeforeEach
- void setUp() throws IOException {
-
- lambdaCodePath = TestLambdaUtils.getTestLambdaCodePath(TEMP_DIR);
-
- this.apiBaseStack = StackUtils.createStack(app, "test-stack", lambdaCodePath.toString(), ENV,
- "test-cdk-bucket", ENV);
- }
-
- @Test
- void should_create_and_return_queue() {
- final String queueId = "test-queue";
-
- final Queue actual = this.apiBaseStack.createQueue(queueId);
-
- assertThat(actual)
- .isNotNull()
- .hasFieldOrProperty("queueName")
- .hasFieldOrProperty("queueArn")
- .hasFieldOrProperty("queueUrl")
- .hasFieldOrProperty("deadLetterQueue")
- .extracting("fifo")
- .isEqualTo(false);
- }
-
- @Test
- void should_create_and_return_fifo_queue() {
- final String queueId = "test-queue";
-
- final Queue actual = this.apiBaseStack.createFifoQueue(queueId, true, MESSAGE_GROUP);
-
- assertThat(actual)
- .isNotNull()
- .hasFieldOrProperty("queueName")
- .hasFieldOrProperty("queueArn")
- .hasFieldOrProperty("queueUrl")
- .hasFieldOrProperty("deadLetterQueue")
- .extracting("fifo")
- .isEqualTo(true);
-
- }
-
- @Test
- void should_create_and_return_dead_letter_queue() {
- final String deadLetterQueueId = "test-dead-letter-queue";
-
- final DeadLetterQueue actual = this.apiBaseStack.createDeadLetterQueue(deadLetterQueueId);
-
- assertThat(actual)
- .isNotNull()
- .hasFieldOrProperty("maxReceiveCount")
- .hasFieldOrProperty("queue");
-
- assertThat(actual.getMaxReceiveCount())
- .isEqualTo(3);
-
- assertThat(actual.getQueue())
- .hasFieldOrProperty("queueName")
- .hasFieldOrProperty("queueArn")
- .hasFieldOrProperty("queueUrl")
- .extracting("fifo")
- .isEqualTo(false);
- }
-
- @Test
- void should_create_and_return_fifo_dead_letter_queue() {
- final String deadLetterQueueId = "test-fifo-dead-letter-queue";
-
- final DeadLetterQueue actual = this.apiBaseStack.createFifoDeadLetterQueue(deadLetterQueueId,
- true, MESSAGE_GROUP);
-
- assertThat(actual)
- .isNotNull()
- .hasFieldOrProperty("maxReceiveCount")
- .hasFieldOrProperty("queue");
-
- assertThat(actual.getMaxReceiveCount())
- .isEqualTo(3);
-
- assertThat(actual.getQueue())
- .isNotNull()
- .hasFieldOrProperty("queueName")
- .hasFieldOrProperty("queueArn")
- .hasFieldOrProperty("queueUrl")
- .extracting("fifo")
- .isEqualTo(true);
- }
-
- @Test
- void should_create_and_return_topic() {
- final String topicId = "test-topic";
-
- final Topic actual = this.apiBaseStack.createTopic(topicId);
-
- assertThat(actual)
- .isNotNull()
- .hasFieldOrProperty("topicName")
- .hasFieldOrProperty("topicArn")
- .extracting("fifo")
- .isEqualTo(false);
- }
-
- @Test
- void should_create_and_return_fifo_topic() {
- final String topicId = "test-topic";
-
- final Topic actual = this.apiBaseStack.createFifoTopic(topicId, true, true);
-
- assertThat(actual)
- .isNotNull()
- .hasFieldOrProperty("topicName")
- .hasFieldOrProperty("topicArn")
- .extracting("fifo")
- .isEqualTo(true);
- }
-
- @Test
- void should_create_and_return_lambda_function() {
- final Vpc vpc = create(this.apiBaseStack, "test-vpc").build();
-
- final Function actual = this.apiBaseStack.createFunction(vpc,
- "test-function",
- "com.coffeebeans.springnativeawslambda.infra.lambda.CustomRuntime2Function::handleRequest",
- Code.fromAsset(this.lambdaCodePath.toString()),
- fromTopicArn(this.apiBaseStack, "success-topic", "arn:aws:sns:us-east-1:***:success-topic"),
- fromRoleArn(this.apiBaseStack, "test-role", "arn:aws:iam::***:role/test-role"),
- Map.of("Account", "***"));
-
- assertThat(actual)
- .isNotNull()
- .hasFieldOrProperty("functionArn")
- .hasFieldOrProperty("role")
- .hasFieldOrProperty("functionName")
- .hasFieldOrProperty("functionArn")
- .hasFieldOrProperty("env")
- .hasFieldOrProperty("architecture")
- .hasFieldOrProperty("runtime")
- .hasFieldOrProperty("timeout");
-
- assertThat(actual.getRuntime())
- .isEqualTo(PROVIDED_AL2023);
- }
-
- @Test
- void should_create_and_return_lambda_rest_api() {
- final Vpc vpc = create(this.apiBaseStack, "test-vpc").build();
-
- final Function function = this.apiBaseStack.createFunction(vpc,
- "test-function",
- "com.coffeebeans.springnativeawslambda.infra.lambda.CustomRuntime2Function::handleRequest",
- Code.fromAsset(this.lambdaCodePath.toString()),
- fromTopicArn(this.apiBaseStack, "success-topic", "arn:aws:sns:us-east-1:***:success-topic"),
- fromRoleArn(this.apiBaseStack, "test-role", "arn:aws:iam::***:role/test-role"),
- Map.of("Account", "***"));
-
- final LambdaRestApi actual = this.apiBaseStack.createLambdaRestApi("test", "rest-api", "name",
- "POST", function, false);
-
- assertThat(actual)
- .isNotNull()
- .hasFieldOrProperty("deploymentStage")
- .hasFieldOrProperty("env")
- .hasFieldOrProperty("restApiName")
- .hasFieldOrProperty("root")
- .hasFieldOrProperty("url")
- .hasFieldOrProperty("restApiRootResourceId")
- .hasFieldOrProperty("restApiId")
- .hasFieldOrProperty("methods");
- }
-}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/LambdaTest.java b/spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/LambdaTest.java
deleted file mode 100644
index d89ccc0..0000000
--- a/spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/LambdaTest.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Licensed to Muhammad Hamadto
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.coffeebeans.springnativeawslambda.infra;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-import java.util.Map;
-
-import static cloud.pianola.cdk.fluent.assertion.CDKStackAssert.assertThat;
-import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE;
-import static software.amazon.awscdk.assertions.Match.exact;
-import static software.amazon.awscdk.assertions.Match.stringLikeRegexp;
-
-class LambdaTest extends TemplateSupport {
-
- public static final String TEST = "test";
-
- @Test
- void should_have_lambda_function() {
-
- assertThat(template)
- .containsFunction("spring-native-aws-lambda-function")
- .hasHandler("org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest")
- .hasCode("test-cdk-bucket", "(.*).zip")
- .hasRole("springnativeawslambdafunctionrole(.*)")
- .hasDependency("springnativeawslambdafunctionrole(.*)")
- .hasDependency("springnativeawslambdafunctionroleDefaultPolicy(.*)")
- .hasTag("COST_CENTRE", KEY_COST_CENTRE)
- .hasTag("ENV", TEST)
- .hasEnvironmentVariable("ENV", TEST)
- .hasEnvironmentVariable("SPRING_PROFILES_ACTIVE", TEST)
- .hasDescription("Lambda example with spring native")
- .hasMemorySize(512)
- .hasRuntime("provided.al2023")
- .hasTimeout(3);
- }
-
- @Test
- void should_have_role_with_AWSLambdaBasicExecutionRole_policy_to_assume_by_lambda() {
- final String principal = "lambda.amazonaws.com";
- final String effect = "Allow";
- final String policyDocumentVersion = "2012-10-17";
- final String managedPolicyArn = ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole";
-
- assertThat(template)
- .containsRoleWithManagedPolicyArn(managedPolicyArn)
- .hasAssumeRolePolicyDocument(principal, null, effect, policyDocumentVersion,
- "sts:AssumeRole");
- }
-
- @Test
- void should_have_default_policy_to_allow_lambda_publish_to_sns() throws JsonProcessingException {
-
- final String policyName = "springnativeawslambdafunctionroleDefaultPolicy(.*)";
- final String deadLetterTopic = "springnativeawslambdafunctiondeadlettertopic(.*)";
- final String action = "sns:Publish";
- final String effect = "Allow";
- final String policyDocumentVersion = "2012-10-17";
-
- assertThat(template)
- .containsPolicy(policyName)
- .isAssociatedWithRole("springnativeawslambdafunctionrole(.*)")
- .hasPolicyDocumentVersion(policyDocumentVersion)
- .hasPolicyDocumentStatement(null,
- deadLetterTopic,
- action,
- effect,
- policyDocumentVersion);
- }
-
- @Test
- void should_have_event_invoke_config_for_success_and_failure() {
-
- final String functionName = "springnativeawslambdafunction(.*)";
-
- assertThat(template)
- .containsLambdaEventInvokeConfig(functionName)
- .hasLambdaEventInvokeConfigQualifier("$LATEST")
- .hasLambdaEventInvokeConfigMaximumRetryAttempts(2);
- }
-
- @Test
- void should_have_permission_to_allow_rest_api_root_call_lambda() {
-
- final List
+
+ io.sandpipers
+ sandpipers-cdk-core
+
+
org.projectlombok
@@ -94,8 +99,8 @@
- cloud.pianola
- cdk-fluent-assertions
+ io.sandpipers
+ sandpipers-cdk-assertions
test
@@ -111,6 +116,12 @@
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
org.mockito
mockito-core
diff --git a/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Application.java b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Application.java
new file mode 100644
index 0000000..44cc4c7
--- /dev/null
+++ b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Application.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to Muhammad Hamadto
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.coffeebeans.springnativeawslambda.infra;
+
+import io.sadpipers.cdk.type.SafeString;
+import io.sandpipers.cdk.core.AbstractApp;
+import org.jetbrains.annotations.NotNull;
+import software.amazon.awscdk.Tags;
+
+import java.util.Map;
+import java.util.Objects;
+
+import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_APPLICATION_VALUE;
+import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_ENV;
+import static com.coffeebeans.springnativeawslambda.infra.Environment.COFFEE_BEANS_DEV_111111111111_AP_SOUTHEAST_2;
+import static com.coffeebeans.springnativeawslambda.infra.Environment.COFFEE_BEANS_PRD_111111111111_AP_SOUTHEAST_2;
+import static com.coffeebeans.springnativeawslambda.infra.TagUtils.createTags;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public final class Application extends AbstractApp {
+ private static final String ENVIRONMENT_NAME_DEV = "dev";
+ private static final String ENVIRONMENT_NAME_PRD = "prd";
+
+
+ private static final String LAMBDA_CODE_PATH = "spring-native-aws-service/target/spring-native-aws-service-native-zip.zip";
+
+ public static void main(final String... args) {
+ final Application app = new Application();
+
+ final String env = System.getenv(KEY_ENV);
+ checkNotNull(env, "'ENVIRONMENT' environment variable is required");
+
+ switch (env) {
+ case ENVIRONMENT_NAME_DEV -> new SpringNativeAwsFunctionStack(app, COFFEE_BEANS_DEV_111111111111_AP_SOUTHEAST_2, LAMBDA_CODE_PATH, env);
+ case ENVIRONMENT_NAME_PRD -> new SpringNativeAwsFunctionStack(app, COFFEE_BEANS_PRD_111111111111_AP_SOUTHEAST_2, LAMBDA_CODE_PATH, env);
+ default -> throw new IllegalArgumentException("Environment name '%s' is not set to a valid value. Set it to '[dev|prd]'".formatted(KEY_ENV));
+ }
+
+ final Map tags = createTags(env, CostCentre.COFFEE_BEANS, app.getApplicationName().getValue());
+ tags.entrySet().stream()
+ .filter(tag -> Objects.nonNull(tag.getValue()))
+ .forEach(tag -> Tags.of(app).add(tag.getKey(), tag.getValue()));
+
+ app.synth();
+ }
+
+ @Override
+ public @NotNull SafeString getApplicationName() {
+ return SafeString.of(KEY_APPLICATION_VALUE);
+ }
+}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Constants.java b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Constants.java
similarity index 54%
rename from spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Constants.java
rename to spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Constants.java
index d628ffa..0205f22 100644
--- a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Constants.java
+++ b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Constants.java
@@ -5,10 +5,8 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Constants {
- public static final String KEY_ENV = "ENV";
+ public static final String KEY_ENV = "ENVIRONMENT";
public static final String KEY_COST_CENTRE = "COST_CENTRE";
- public static final String KEY_APPLICATION_NAME = "applicationName";
-
- public static final String VALUE_COST_CENTRE = "coffeeBeans-core";
-
+ public static final String KEY_APPLICATION_NAME = "APPLICATION_NAME";
+ public static final String KEY_APPLICATION_VALUE = "spring-native-aws-function";
}
diff --git a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/model/Request.java b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/CostCentre.java
similarity index 57%
rename from spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/model/Request.java
rename to spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/CostCentre.java
index 81d1995..06fe7ad 100644
--- a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/model/Request.java
+++ b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/CostCentre.java
@@ -1,5 +1,5 @@
/*
- * Licensed to Muhammad Hamadto
+ * Licensed to Muhammad Hamadto
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,23 +13,24 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
*/
-package com.coffeebeans.springnativeawslambda.model;
+package com.coffeebeans.springnativeawslambda.infra;
+
+import io.sadpipers.cdk.type.AlphanumericString;
+import io.sandpipers.cdk.core.AbstractCostCentre;
+import lombok.Getter;
+import lombok.experimental.SuperBuilder;
-import jakarta.validation.constraints.NotBlank;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+@Getter
+@SuperBuilder
+public class CostCentre extends AbstractCostCentre {
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class Request {
+ public static final CostCentre COFFEE_BEANS = CostCentre.builder()
+ .value(AlphanumericString.of("cbcore"))
+ .build();
- @NotBlank
- private String name;
+ static {
+ registerCostCentre(COFFEE_BEANS);
+ }
}
diff --git a/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Environment.java b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Environment.java
new file mode 100644
index 0000000..ef8c0e6
--- /dev/null
+++ b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/Environment.java
@@ -0,0 +1,54 @@
+package com.coffeebeans.springnativeawslambda.infra;
+
+
+import io.sadpipers.cdk.type.AWSAccount;
+import io.sadpipers.cdk.type.SafeString;
+import io.sandpipers.cdk.core.AbstractEnvironment;
+import lombok.Getter;
+import lombok.experimental.SuperBuilder;
+
+import static io.sandpipers.cdk.core.util.Constants.AWS_REGION_AP_SOUTHEAST_2;
+
+@Getter
+@SuperBuilder
+public class Environment extends AbstractEnvironment {
+
+ public static final Environment COFFEE_BEANS_DEV_111111111111_AP_SOUTHEAST_2;
+ public static final Environment COFFEE_BEANS_PRD_111111111111_AP_SOUTHEAST_2;
+ public static final Environment COFFEE_BEANS_TEST_111111111111_AP_SOUTHEAST_2;
+
+ static {
+ final AWSAccount awsAccount = AWSAccount.of("111111111111");
+ final SafeString awsRegion = SafeString.of(AWS_REGION_AP_SOUTHEAST_2);
+
+ final software.amazon.awscdk.Environment awsEnvironment = software.amazon.awscdk.Environment.builder()
+ .account(awsAccount.getValue())
+ .account(awsRegion.getValue())
+ .build();
+
+ COFFEE_BEANS_DEV_111111111111_AP_SOUTHEAST_2 = Environment.builder()
+ .awsEnvironment(awsEnvironment)
+ .costCentre(CostCentre.COFFEE_BEANS)
+ .environmentName(SafeString.of("DEV"))
+ .environmentKey(SafeString.of("COFFEE_BEANS_TEST_111111111111_AP_SOUTHEAST_2"))
+ .build();
+
+ COFFEE_BEANS_PRD_111111111111_AP_SOUTHEAST_2 = Environment.builder()
+ .awsEnvironment(awsEnvironment)
+ .costCentre(CostCentre.COFFEE_BEANS)
+ .environmentName(SafeString.of("PRD"))
+ .environmentKey(SafeString.of("COFFEE_BEANS_PRD_111111111111_AP_SOUTHEAST_2"))
+ .build();
+
+ COFFEE_BEANS_TEST_111111111111_AP_SOUTHEAST_2 = Environment.builder()
+ .awsEnvironment(awsEnvironment)
+ .costCentre(CostCentre.COFFEE_BEANS)
+ .environmentName(SafeString.of("TEST"))
+ .environmentKey(SafeString.of("COFFEE_BEANS_TEST_111111111111_AP_SOUTHEAST_2"))
+ .build();
+
+ registerEnvironment(COFFEE_BEANS_DEV_111111111111_AP_SOUTHEAST_2);
+ registerEnvironment(COFFEE_BEANS_PRD_111111111111_AP_SOUTHEAST_2);
+ registerEnvironment(COFFEE_BEANS_TEST_111111111111_AP_SOUTHEAST_2);
+ }
+}
diff --git a/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/SpringNativeAwsFunctionStack.java b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/SpringNativeAwsFunctionStack.java
new file mode 100644
index 0000000..ace88d6
--- /dev/null
+++ b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/SpringNativeAwsFunctionStack.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to Muhammad Hamadto
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.coffeebeans.springnativeawslambda.infra;
+
+import io.sadpipers.cdk.type.KebabCaseString;
+import io.sadpipers.cdk.type.SafeString;
+import io.sandpipers.cdk.core.AbstractEnvironment;
+import io.sandpipers.cdk.core.construct.BaseStack;
+import io.sandpipers.cdk.core.construct.dynamodb.TableV2;
+import io.sandpipers.cdk.core.construct.dynamodb.TableV2.TableProps;
+import io.sandpipers.cdk.core.construct.lambda.CustomRuntime2023Function;
+import io.sandpipers.cdk.core.construct.lambda.CustomRuntime2023Function.CustomRuntime2023FunctionProps;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+import software.amazon.awscdk.Duration;
+import software.amazon.awscdk.RemovalPolicy;
+import software.amazon.awscdk.services.apigateway.LambdaRestApi;
+import software.amazon.awscdk.services.apigateway.Resource;
+import software.amazon.awscdk.services.apigateway.StageOptions;
+import software.amazon.awscdk.services.dynamodb.Attribute;
+import software.amazon.awscdk.services.dynamodb.AttributeType;
+import software.amazon.awscdk.services.iam.IManagedPolicy;
+import software.amazon.awscdk.services.iam.Role;
+import software.amazon.awscdk.services.iam.ServicePrincipal;
+import software.amazon.awscdk.services.lambda.AssetCode;
+import software.amazon.awscdk.services.lambda.Function;
+
+import javax.validation.constraints.NotBlank;
+import java.util.List;
+import java.util.Map;
+
+import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_ENV;
+import static software.amazon.awscdk.services.iam.ManagedPolicy.fromAwsManagedPolicyName;
+import static software.amazon.awscdk.services.lambda.Code.fromAsset;
+
+public class SpringNativeAwsFunctionStack extends BaseStack {
+
+ private static final int LAMBDA_FUNCTION_TIMEOUT_IN_SECONDS = 3;
+ private static final int LAMBDA_FUNCTION_MEMORY_SIZE = 512;
+ private static final int LAMBDA_FUNCTION_RETRY_ATTEMPTS = 2;
+ private static final String LAMBDA_HANDLER = "org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest";
+ private static final String ENVIRONMENT_VARIABLE_SPRING_PROFILES_ACTIVE = "SPRING_PROFILES_ACTIVE";
+
+ public SpringNativeAwsFunctionStack(@NotNull final Application app,
+ @NotNull final AbstractEnvironment environment,
+ @NotBlank final String lambdaCodePath,
+ @NotBlank final String stage) {
+ super(app, environment);
+
+ final List managedPolicies =
+ List.of(fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"));
+
+ final Role role = Role.Builder.create(this, "Role")
+ .assumedBy(new ServicePrincipal("lambda.amazonaws.com"))
+ .managedPolicies(managedPolicies)
+ .build();
+
+ final AssetCode assetCode = fromAsset(lambdaCodePath);
+
+ final Map lambdaEnvironment = Map.of(ENVIRONMENT_VARIABLE_SPRING_PROFILES_ACTIVE, stage, KEY_ENV, stage);
+
+ final CustomRuntime2023FunctionProps functionProps = CustomRuntime2023FunctionProps.builder()
+ .description("Example of a Spring Native AWS Lambda Function using CDK")
+ .code(assetCode)
+ .handler(LAMBDA_HANDLER)
+ .role(role)
+ .environment(lambdaEnvironment)
+ .deadLetterQueueEnabled(true)
+ .timeout(Duration.seconds(LAMBDA_FUNCTION_TIMEOUT_IN_SECONDS))
+ .memorySize(LAMBDA_FUNCTION_MEMORY_SIZE)
+ .retryAttempts(LAMBDA_FUNCTION_RETRY_ATTEMPTS)
+ .build();
+
+ final Function function = new CustomRuntime2023Function<>(this, SafeString.of("Lambda"), functionProps)
+ .getFunction();
+
+ final TableProps tableProps = TableProps.builder()
+ .partitionKey(Attribute.builder().name("id").type(AttributeType.STRING).build())
+ .timeToLiveAttribute("creationTime")
+ .tableName(KebabCaseString.of("secrets"))
+ .removalPolicy(RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE)
+ .build();
+
+ final software.amazon.awscdk.services.dynamodb.TableV2 tableV2 = new TableV2(this, SafeString.of("Table"), tableProps).getTable();
+
+ tableV2.grantWriteData(function);
+ tableV2.grantReadData(function);
+
+ // point to the lambda
+ final LambdaRestApi lambdaRestApi = LambdaRestApi.Builder.create(this, "RestApi")
+ .handler(function)
+ .proxy(true)
+ .deployOptions(StageOptions.builder().stageName(stage).build())
+ .build();
+
+ // get root resource to add methods
+ final Resource resource = lambdaRestApi.getRoot().addResource("variables");
+ resource.addMethod(StringUtils.toRootUpperCase("ANY"));
+ resource.addMethod(StringUtils.toRootUpperCase("GET"));
+ }
+}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/TagUtils.java b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/TagUtils.java
similarity index 78%
rename from spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/TagUtils.java
rename to spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/TagUtils.java
index 7153229..67d975c 100644
--- a/spring-native-aws-lambda-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/TagUtils.java
+++ b/spring-native-aws-service-infra/src/main/java/com/coffeebeans/springnativeawslambda/infra/TagUtils.java
@@ -24,6 +24,7 @@
import javax.validation.constraints.NotBlank;
import java.util.Map;
+import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_APPLICATION_NAME;
import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE;
import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_ENV;
@@ -31,7 +32,12 @@
public final class TagUtils {
public static Map createTags(@NotBlank final String env,
- @NotBlank final String costCentre) {
- return Map.of(KEY_ENV, env, KEY_COST_CENTRE, costCentre);
+ @NotBlank final CostCentre costCentre,
+ @NotBlank final String applicationName) {
+ return Map.of(
+ KEY_ENV, env,
+ KEY_COST_CENTRE, costCentre.getValueAsString(),
+ KEY_APPLICATION_NAME, applicationName
+ );
}
}
diff --git a/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/ApiBaseStackTest.java b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/ApiBaseStackTest.java
new file mode 100644
index 0000000..5e95dbf
--- /dev/null
+++ b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/ApiBaseStackTest.java
@@ -0,0 +1,225 @@
+///*
+// * Licensed to Muhammad Hamadto
+// *
+// * Licensed under the Apache License, Version 2.0 (the "License");
+// * you may not use this file except in compliance with the License.
+// * You may obtain a copy of the License at
+// * http://www.apache.org/licenses/LICENSE-2.0
+// *
+// * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// *
+// * Unless required by applicable law or agreed to in writing, software
+// * distributed under the License is distributed on an "AS IS" BASIS,
+// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// * See the License for the specific language governing permissions and
+// * limitations under the License.
+// *
+// */
+//
+//package com.coffeebeans.springnativeawslambda.infra;
+//
+//import static org.assertj.core.api.Assertions.assertThat;
+//import static software.amazon.awscdk.services.ec2.Vpc.Builder.create;
+//import static software.amazon.awscdk.services.iam.Role.fromRoleArn;
+//import static software.amazon.awscdk.services.lambda.Runtime.PROVIDED_AL2023;
+//import static software.amazon.awscdk.services.sns.Topic.fromTopicArn;
+//import static software.amazon.awscdk.services.sqs.DeduplicationScope.MESSAGE_GROUP;
+//
+//import java.io.IOException;
+//import java.nio.file.Path;
+//import java.util.Map;
+//import org.junit.jupiter.api.BeforeEach;
+//import org.junit.jupiter.api.Test;
+//import org.junit.jupiter.api.io.TempDir;
+//import software.amazon.awscdk.App;
+//import software.amazon.awscdk.services.apigateway.LambdaRestApi;
+//import software.amazon.awscdk.services.ec2.Vpc;
+//import software.amazon.awscdk.services.lambda.Code;
+//import software.amazon.awscdk.services.lambda.Function;
+//import software.amazon.awscdk.services.sns.Topic;
+//import software.amazon.awscdk.services.sqs.DeadLetterQueue;
+//import software.amazon.awscdk.services.sqs.Queue;
+//
+//class ApiBaseStackTest {
+//
+// private static final String ENV = "test";
+//
+// private ApiBaseStack apiBaseStack;
+//
+// private final App app = new App();
+//
+// private Path lambdaCodePath;
+//
+// @TempDir
+// private static Path TEMP_DIR;
+//
+// @BeforeEach
+// void setUp() throws IOException {
+//
+// lambdaCodePath = TestLambdaUtils.getTestLambdaCodePath(TEMP_DIR);
+//
+// this.apiBaseStack = StackUtils.createStack(app, "test-stack", lambdaCodePath.toString(), ENV,
+// "test-cdk-bucket", ENV);
+// }
+//
+// @Test
+// void should_create_and_return_queue() {
+// final String queueId = "test-queue";
+//
+// final Queue actual = this.apiBaseStack.createQueue(queueId);
+//
+// assertThat(actual)
+// .isNotNull()
+// .hasFieldOrProperty("queueName")
+// .hasFieldOrProperty("queueArn")
+// .hasFieldOrProperty("queueUrl")
+// .hasFieldOrProperty("deadLetterQueue")
+// .extracting("fifo")
+// .isEqualTo(false);
+// }
+//
+// @Test
+// void should_create_and_return_fifo_queue() {
+// final String queueId = "test-queue";
+//
+// final Queue actual = this.apiBaseStack.createFifoQueue(queueId, true, MESSAGE_GROUP);
+//
+// assertThat(actual)
+// .isNotNull()
+// .hasFieldOrProperty("queueName")
+// .hasFieldOrProperty("queueArn")
+// .hasFieldOrProperty("queueUrl")
+// .hasFieldOrProperty("deadLetterQueue")
+// .extracting("fifo")
+// .isEqualTo(true);
+//
+// }
+//
+// @Test
+// void should_create_and_return_dead_letter_queue() {
+// final String deadLetterQueueId = "test-dead-letter-queue";
+//
+// final DeadLetterQueue actual = this.apiBaseStack.createDeadLetterQueue(deadLetterQueueId);
+//
+// assertThat(actual)
+// .isNotNull()
+// .hasFieldOrProperty("maxReceiveCount")
+// .hasFieldOrProperty("queue");
+//
+// assertThat(actual.getMaxReceiveCount())
+// .isEqualTo(3);
+//
+// assertThat(actual.getQueue())
+// .hasFieldOrProperty("queueName")
+// .hasFieldOrProperty("queueArn")
+// .hasFieldOrProperty("queueUrl")
+// .extracting("fifo")
+// .isEqualTo(false);
+// }
+//
+// @Test
+// void should_create_and_return_fifo_dead_letter_queue() {
+// final String deadLetterQueueId = "test-fifo-dead-letter-queue";
+//
+// final DeadLetterQueue actual = this.apiBaseStack.createFifoDeadLetterQueue(deadLetterQueueId,
+// true, MESSAGE_GROUP);
+//
+// assertThat(actual)
+// .isNotNull()
+// .hasFieldOrProperty("maxReceiveCount")
+// .hasFieldOrProperty("queue");
+//
+// assertThat(actual.getMaxReceiveCount())
+// .isEqualTo(3);
+//
+// assertThat(actual.getQueue())
+// .isNotNull()
+// .hasFieldOrProperty("queueName")
+// .hasFieldOrProperty("queueArn")
+// .hasFieldOrProperty("queueUrl")
+// .extracting("fifo")
+// .isEqualTo(true);
+// }
+//
+// @Test
+// void should_create_and_return_topic() {
+// final String topicId = "test-topic";
+//
+// final Topic actual = this.apiBaseStack.createTopic(topicId);
+//
+// assertThat(actual)
+// .isNotNull()
+// .hasFieldOrProperty("topicName")
+// .hasFieldOrProperty("topicArn")
+// .extracting("fifo")
+// .isEqualTo(false);
+// }
+//
+// @Test
+// void should_create_and_return_fifo_topic() {
+// final String topicId = "test-topic";
+//
+// final Topic actual = this.apiBaseStack.createFifoTopic(topicId, true, true);
+//
+// assertThat(actual)
+// .isNotNull()
+// .hasFieldOrProperty("topicName")
+// .hasFieldOrProperty("topicArn")
+// .extracting("fifo")
+// .isEqualTo(true);
+// }
+//
+// @Test
+// void should_create_and_return_lambda_function() {
+// final Vpc vpc = create(this.apiBaseStack, "test-vpc").build();
+//
+// final Function actual = this.apiBaseStack.createFunction(vpc,
+// "test-function",
+// "com.coffeebeans.springnativeawslambda.infra.lambda.CustomRuntime2Function::handleRequest",
+// Code.fromAsset(this.lambdaCodePath.toString()),
+// fromTopicArn(this.apiBaseStack, "success-topic", "arn:aws:sns:us-east-1:***:success-topic"),
+// fromRoleArn(this.apiBaseStack, "test-role", "arn:aws:iam::***:role/test-role"),
+// Map.of("Account", "***"));
+//
+// assertThat(actual)
+// .isNotNull()
+// .hasFieldOrProperty("functionArn")
+// .hasFieldOrProperty("role")
+// .hasFieldOrProperty("functionName")
+// .hasFieldOrProperty("functionArn")
+// .hasFieldOrProperty("env")
+// .hasFieldOrProperty("architecture")
+// .hasFieldOrProperty("runtime")
+// .hasFieldOrProperty("timeout");
+//
+// assertThat(actual.getRuntime())
+// .isEqualTo(PROVIDED_AL2023);
+// }
+//
+// @Test
+// void should_create_and_return_lambda_rest_api() {
+// final Vpc vpc = create(this.apiBaseStack, "test-vpc").build();
+//
+// final Function function = this.apiBaseStack.createFunction(vpc,
+// "test-function",
+// "com.coffeebeans.springnativeawslambda.infra.lambda.CustomRuntime2Function::handleRequest",
+// Code.fromAsset(this.lambdaCodePath.toString()),
+// fromTopicArn(this.apiBaseStack, "success-topic", "arn:aws:sns:us-east-1:***:success-topic"),
+// fromRoleArn(this.apiBaseStack, "test-role", "arn:aws:iam::***:role/test-role"),
+// Map.of("Account", "***"));
+//
+// final LambdaRestApi actual = this.apiBaseStack.createLambdaRestApi("test", "rest-api", "name",
+// "POST", function, false);
+//
+// assertThat(actual)
+// .isNotNull()
+// .hasFieldOrProperty("deploymentStage")
+// .hasFieldOrProperty("env")
+// .hasFieldOrProperty("restApiName")
+// .hasFieldOrProperty("root")
+// .hasFieldOrProperty("url")
+// .hasFieldOrProperty("restApiRootResourceId")
+// .hasFieldOrProperty("restApiId")
+// .hasFieldOrProperty("methods");
+// }
+//}
\ No newline at end of file
diff --git a/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/LambdaTest.java b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/LambdaTest.java
new file mode 100644
index 0000000..40e531e
--- /dev/null
+++ b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/LambdaTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to Muhammad Hamadto
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.coffeebeans.springnativeawslambda.infra;
+
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import java.util.Map;
+
+import static io.sandpipers.cdk.assertion.CDKStackAssert.assertThat;
+
+class LambdaTest extends TemplateSupport {
+
+ public static final String TEST = "test";
+
+ @Test
+ void should_have_lambda_function() {
+
+ assertThat(template)
+ .containsFunction("^Lambda[A-Z0-9]{8}$")
+ .hasHandler("org.springframework.cloud.function.adapter.aws.FunctionInvoker::handleRequest")
+ .hasCode("cdk-cbcore-assets-\\$\\{AWS\\:\\:AccountId\\}-\\$\\{AWS\\:\\:Region\\}", "(.*).zip")
+ .hasRole("^Role[A-Z0-9]{8}$")
+ .hasDependency("^RoleDefaultPolicy[A-Z0-9]{8}$")
+ .hasDependency("^Role[A-Z0-9]{8}$")
+ .hasTag("COST_CENTRE", "cbcore")
+ .hasTag("ENVIRONMENT", TEST)
+ .hasTag("APPLICATION_NAME", "spring-native-aws-function")
+ .hasEnvironmentVariable("ENVIRONMENT", TEST)
+ .hasEnvironmentVariable("SPRING_PROFILES_ACTIVE", TEST)
+ .hasDescription("Example of a Spring Native AWS Lambda Function using CDK")
+ .hasMemorySize(512)
+ .hasRuntime("provided.al2023")
+ .hasTimeout(3)
+ .hasDeadLetterTarget("^LambdaDeadLetterQueue[A-Z0-9]{8}$");
+ }
+
+ @Test
+ void should_have_role_with_AWSLambdaBasicExecutionRole_policy_to_assume_by_lambda() {
+ final Map principal = Map.of("Service", "lambda.amazonaws.com");
+ final String effect = "Allow";
+ final String policyDocumentVersion = "2012-10-17";
+ final String managedPolicyArn = ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole";
+
+ assertThat(template)
+ .containsRole("^Role[A-Z0-9]{8}$")
+ .hasManagedPolicyArn(managedPolicyArn)
+ .hasAssumeRolePolicyDocument(principal, null, effect, policyDocumentVersion, "sts:AssumeRole");
+ }
+
+ @Test
+ @Disabled
+ void should_have_default_policy_to_allow_lambda_send_messages_to_sqs() {
+
+ assertThat(template)
+ .containsPolicy("^RoleDefaultPolicy[A-Z0-9]{8}$")
+ .isAssociatedWithRole("^Role[A-Z0-9]{8}$")
+ .hasPolicyDocumentStatement(null,
+ "^LambdaDeadLetterQueue[A-Z0-9]{8}$",
+ "sqs:SendMessage",
+ "Allow",
+ "2012-10-17");
+ }
+
+ @Test
+ void should_have_event_invoke_config_for_success_and_failure() {
+
+ assertThat(template)
+ .containsLambdaEventInvokeConfig("^LambdaEventInvokeConfig[A-Z0-9]{8}$")
+ .hasQualifier("$LATEST")
+ .hasFunctionName("^Lambda[A-Z0-9]{8}$")
+ .hasMaximumEventAgeInSeconds(60)
+ .hasMaximumRetryAttempts(2);
+ }
+
+ @ParameterizedTest
+ @CsvSource(
+ {
+ "^RestApiproxyANYApiPermissionSpringNativeAwsFunctionStackRestApi[A-Z0-9]{8}ANYproxy[A-Z0-9]{8}$, arn:aws:execute-api::AWS::AccountId:RestApi[A-Z0-9]{8}/RestApiDeploymentStagetest[A-Z0-9]{8}/\\*/\\*",
+ "^RestApiproxyANYApiPermissionTestSpringNativeAwsFunctionStackRestApi[A-Z0-9]{8}ANYproxy[A-Z0-9]{8}$, arn:aws:execute-api::AWS::AccountId:RestApi[A-Z0-9]{8}/test-invoke-stage/\\*/\\*",
+ "^RestApiANYApiPermissionSpringNativeAwsFunctionStackRestApi[A-Z0-9]{8}ANY[A-Z0-9]{8}$, arn:aws:execute-api::AWS::AccountId:RestApi[A-Z0-9]{8}/RestApiDeploymentStagetest[A-Z0-9]{8}/\\*/",
+ "^RestApiANYApiPermissionTestSpringNativeAwsFunctionStackRestApi[A-Z0-9]{8}ANY[A-Z0-9]{8}$, arn:aws:execute-api::AWS::AccountId:RestApi[A-Z0-9]{8}/test-invoke-stage/\\*/",
+ "^RestApivariablesANYApiPermissionSpringNativeAwsFunctionStackRestApi[A-Z0-9]{8}ANYvariables[A-Z0-9]{8}$, arn:aws:execute-api::AWS::AccountId:RestApi[A-Z0-9]{8}/RestApiDeploymentStagetest[A-Z0-9]{8}/\\*/variables",
+ "^RestApivariablesANYApiPermissionTestSpringNativeAwsFunctionStackRestApi[A-Z0-9]{8}ANYvariables[A-Z0-9]{8}$, arn:aws:execute-api::AWS::AccountId:RestApi[A-Z0-9]{8}/test-invoke-stage/\\*/variables",
+ "^RestApivariablesGETApiPermissionSpringNativeAwsFunctionStackRestApi[A-Z0-9]{8}GETvariables[A-Z0-9]{8}$, arn:aws:execute-api::AWS::AccountId:RestApi[A-Z0-9]{8}/RestApiDeploymentStagetest[A-Z0-9]{8}/GET/variables",
+ "^RestApivariablesGETApiPermissionTestSpringNativeAwsFunctionStackRestApi[A-Z0-9]{8}GETvariables[A-Z0-9]{8}$, arn:aws:execute-api::AWS::AccountId:RestApi[A-Z0-9]{8}/test-invoke-stage/GET/variables"
+ }
+ )
+ void should_have_permission_to_allow_rest_api_to_call_lambda(final String lambdaPermissionResourceId,
+ final String sourceArnPattern) {
+
+ assertThat(template)
+ .containsLambdaPermission(lambdaPermissionResourceId)
+ .hasLambdaPermission("^Lambda[A-Z0-9]{8}$",
+ "lambda:InvokeFunction",
+ "apigateway.amazonaws.com",
+ sourceArnPattern);
+ }
+}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TopicTest.java b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/QueueTest.java
similarity index 71%
rename from spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TopicTest.java
rename to spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/QueueTest.java
index 401bc02..6b1cda1 100644
--- a/spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TopicTest.java
+++ b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/QueueTest.java
@@ -20,18 +20,18 @@
import org.junit.jupiter.api.Test;
-import static cloud.pianola.cdk.fluent.assertion.CDKStackAssert.assertThat;
-import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE;
+import static io.sandpipers.cdk.assertion.CDKStackAssert.assertThat;
-class TopicTest extends TemplateSupport {
+class QueueTest extends TemplateSupport {
public static final String TEST = "test";
@Test
void should_have_dead_letter_topic() {
assertThat(template)
- .containsTopic("spring-native-aws-lambda-function-dead-letter-topic")
- .hasTag("COST_CENTRE", KEY_COST_CENTRE)
- .hasTag("ENV", TEST);
+ .containsQueue("^LambdaDeadLetterQueue[A-Z0-9]{8}$")
+ .hasTag("COST_CENTRE", "cbcore")
+ .hasTag("ENVIRONMENT", TEST)
+ .hasTag("APPLICATION_NAME", "spring-native-aws-function");
}
-}
\ No newline at end of file
+}
diff --git a/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/RestApiTest.java b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/RestApiTest.java
new file mode 100644
index 0000000..6a36215
--- /dev/null
+++ b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/RestApiTest.java
@@ -0,0 +1,166 @@
+///*
+// * Licensed to Muhammad Hamadto
+// *
+// * Licensed under the Apache License, Version 2.0 (the "License");
+// * you may not use this file except in compliance with the License.
+// * You may obtain a copy of the License at
+// * http://www.apache.org/licenses/LICENSE-2.0
+// *
+// * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// *
+// * Unless required by applicable law or agreed to in writing, software
+// * distributed under the License is distributed on an "AS IS" BASIS,
+// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// * See the License for the specific language governing permissions and
+// * limitations under the License.
+// *
+// */
+//
+//package com.coffeebeans.springnativeawslambda.infra;
+//
+//import org.junit.jupiter.api.Test;
+//import software.amazon.awscdk.assertions.Match;
+//
+//import java.util.List;
+//import java.util.Map;
+//
+//import static com.coffeebeans.cdk.assertion.CDKStackAssert.assertThat;
+//import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE;
+//
+//class RestApiTest extends TemplateSupport {
+//
+// public static final String TEST = "test";
+//
+// @Test
+// void should_have_rest_api() {
+//
+// assertThat(template)
+// .containsRestApi("^LambdaRestApi[a-zA-Z0-9]{8}$")
+// .hasTag("APPLICATION_NAME", "apigateway-cdk-example")
+// .hasTag("COST_CENTRE", "CoffeeBeans")
+// .hasTag("ENVIRONMENT", TEST);
+// }
+//
+// @Test
+// void should_have_rest_api_account() {
+// final String cloudWatchRoleArn = "springnativeawslambdafunctionrestapiCloudWatchRole(.*)";
+// final String dependency = "springnativeawslambdafunctionrestapi(.*)";
+//
+// assertThat(template)
+// .containsRestApiAccountWithCloudWatchRoleArn(cloudWatchRoleArn)
+// .hasDependency(dependency)
+// .hasUpdateReplacePolicy("Retain")
+// .hasDeletionPolicy("Retain");
+// }
+//
+// @Test
+// void should_have_rest_api_deployment() {
+//
+// assertThat(template)
+// .containsRestApiDeployment("springnativeawslambdafunctionrestapi(.*)")
+// .hasDependency("springnativeawslambdafunctionrestapiproxyANY(.*)")
+// .hasDependency("springnativeawslambdafunctionrestapiproxy(.*)")
+// .hasDependency("springnativeawslambdafunctionrestapiANY(.*)")
+// .hasDependency("springnativeawslambdafunctionrestapinamePOST(.*)")
+// .hasDependency("springnativeawslambdafunctionrestapiname(.*)")
+// .hasDescription("Automatically created by the RestApi construct");
+// }
+//
+// @Test
+// void should_have_rest_api_stage() {
+//
+// assertThat(template)
+// .containsRestApiStage("test")
+// .hasRestApiId(("springnativeawslambdafunctionrestapi(.*)"))
+// .hasDeploymentId(("springnativeawslambdafunctionrestapiDeployment(.*)"))
+// .hasDependency("springnativeawslambdafunctionrestapiAccount(.*)")
+// .hasTag("COST_CENTRE", KEY_COST_CENTRE)
+// .hasTag("ENV", TEST);
+// }
+//
+// @Test
+// void should_have_proxy_resource() {
+// final String restApiId = "springnativeawslambdafunctionrestapi(.*)";
+//
+// final Map> parentId = Map.of(
+// "Fn::GetAtt", List.of(Match.stringLikeRegexp(restApiId), "RootResourceId")
+// );
+//
+// assertThat(template)
+// .containsRestApiResource("{proxy+}", restApiId, parentId)
+// .hasRestApiId(restApiId)
+// .hasParentId(restApiId);
+// }
+//
+// @Test
+// void should_have_account_resource() {
+// final String restApiId = "springnativeawslambdafunctionrestapi(.*)";
+//
+// final Map> parentId = Map.of(
+// "Fn::GetAtt", List.of(Match.stringLikeRegexp(restApiId), "RootResourceId")
+// );
+//
+// assertThat(template)
+// .containsRestApiResource("name", restApiId, parentId)
+// .hasRestApiId(restApiId)
+// .hasParentId(restApiId);
+// }
+//
+// @Test
+// void should_have_post_method() {
+//
+// final String integrationType = "AWS_PROXY";
+// final String httpMethod = "POST";
+//
+// assertThat(template)
+// .containsNonRootRestApiMethod(httpMethod, "springnativeawslambdafunctionrestapiname(.*)")
+// .hasHttpMethod(httpMethod)
+// .hasIntegration(httpMethod, integrationType)
+// .hasAuthorizationType("NONE")
+// .hasRestApiId(("springnativeawslambdafunctionrestapi(.*)"));
+// }
+//
+// @Test
+// void should_have_proxy_method() {
+//
+// final String method = "ANY";
+// final String integrationType = "AWS_PROXY";
+//
+// assertThat(template)
+// .containsApiMethod( "springnativeawslambdafunctionrestapiproxy(.*)")
+// .hasHttpMethod(method)
+// .hasIntegration("POST", integrationType)
+// .hasAuthorizationType("NONE")
+// .hasRestApiId(("springnativeawslambdafunctionrestapi(.*)"));
+// }
+//
+// @Test
+// void should_have_root_method() {
+//
+// final String method = "ANY";
+// final String integrationType = "AWS_PROXY";
+//
+// assertThat(template)
+// .containsRootRestApiMethod(method, "springnativeawslambdafunctionrestapi(.*)")
+// .hasHttpMethod(method)
+// .hasIntegration("POST", integrationType)
+// .hasAuthorizationType("NONE")
+// .hasRestApiId(("springnativeawslambdafunctionrestapi(.*)"));
+// }
+//
+// @Test
+// void should_have_role_with_AmazonAPIGatewayPushToCloudWatchLogs_policy_for_rest_api_to_push_logs_to_cloud_watch() {
+// final String principal = "apigateway.amazonaws.com";
+// final String effect = "Allow";
+// final String policyDocumentVersion = "2012-10-17";
+// final String managedPolicyArn = ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs";
+//
+// assertThat(template)
+// .containsRoleWithManagedPolicyArn(managedPolicyArn)
+// .hasAssumeRolePolicyDocument(principal,
+// null,
+// effect,
+// policyDocumentVersion,
+// "sts:AssumeRole");
+// }
+//}
\ No newline at end of file
diff --git a/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TagUtilsTest.java b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TagUtilsTest.java
new file mode 100644
index 0000000..46d95ba
--- /dev/null
+++ b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TagUtilsTest.java
@@ -0,0 +1,44 @@
+///*
+// * Licensed to Muhammad Hamadto
+// *
+// * Licensed under the Apache License, Version 2.0 (the "License");
+// * you may not use this file except in compliance with the License.
+// * You may obtain a copy of the License at
+// * http://www.apache.org/licenses/LICENSE-2.0
+// *
+// * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// *
+// * Unless required by applicable law or agreed to in writing, software
+// * distributed under the License is distributed on an "AS IS" BASIS,
+// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// * See the License for the specific language governing permissions and
+// * limitations under the License.
+// *
+// */
+//
+//package com.coffeebeans.springnativeawslambda.infra;
+//
+//import org.junit.jupiter.api.Test;
+//
+//import java.util.Map;
+//
+//import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE;
+//import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_ENV;
+//import static org.assertj.core.api.Assertions.assertThat;
+//
+//class TagUtilsTest {
+//
+// @Test
+// void should_create_and_return_tag_map() {
+// // given
+// final String env = "test";
+// final String costCentre = "coffeeBeans-core";
+//
+// // when
+// final Map tags = TagUtils.createTags(env, costCentre);
+//
+// assertThat(tags)
+// .containsEntry(KEY_ENV, env)
+// .containsEntry(KEY_COST_CENTRE, costCentre);
+// }
+//}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TemplateSupport.java b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TemplateSupport.java
similarity index 51%
rename from spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TemplateSupport.java
rename to spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TemplateSupport.java
index 349ff25..9f5e00f 100644
--- a/spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TemplateSupport.java
+++ b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TemplateSupport.java
@@ -18,6 +18,15 @@
package com.coffeebeans.springnativeawslambda.infra;
+import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_APPLICATION_VALUE;
+import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE;
+import static com.coffeebeans.springnativeawslambda.infra.Environment.COFFEE_BEANS_TEST_111111111111_AP_SOUTHEAST_2;
+import static com.coffeebeans.springnativeawslambda.infra.TagUtils.createTags;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Objects;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.io.TempDir;
@@ -25,42 +34,33 @@
import software.amazon.awscdk.Tags;
import software.amazon.awscdk.assertions.Template;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.Map;
-import java.util.Objects;
-
-import static com.coffeebeans.springnativeawslambda.infra.Constants.KEY_COST_CENTRE;
-import static com.coffeebeans.springnativeawslambda.infra.TagUtils.createTags;
-
public abstract class TemplateSupport {
- public static final String ENV = "test";
- public static final String TEST_CDK_BUCKET = "test-cdk-bucket";
- public static final String QUALIFIER = "test";
- static Template template;
- private static final String STACK_NAME = "spring-native-aws-lambda-function-test-stack";
- @TempDir
- private static Path TEMP_DIR;
+ public static final String ENV = "test";
+ public static final String TEST_CDK_BUCKET = "test-cdk-bucket";
+ public static final String QUALIFIER = "test";
+ static Template template;
+ private static final String STACK_NAME = "spring-native-aws-function-test-stack";
+ @TempDir
+ private static Path TEMP_DIR;
- @BeforeAll
- static void initAll() throws IOException {
- final Path lambdaCodePath = TestLambdaUtils.getTestLambdaCodePath(TEMP_DIR);
+ @BeforeAll
+ static void initAll() throws IOException {
+ final Path lambdaCodePath = TestLambdaUtils.getTestLambdaCodePath(TEMP_DIR);
- final Map tags = createTags(ENV, KEY_COST_CENTRE);
- final App app = new App();
- final SpringNativeAwsLambdaStack stack = StackUtils.createStack(app, STACK_NAME,
- lambdaCodePath.toString(), QUALIFIER, TEST_CDK_BUCKET, ENV);
+ final Map tags = createTags(ENV, CostCentre.COFFEE_BEANS, KEY_APPLICATION_VALUE);
+ final Application app = new Application();
+ final SpringNativeAwsFunctionStack stack = new SpringNativeAwsFunctionStack(app, COFFEE_BEANS_TEST_111111111111_AP_SOUTHEAST_2, lambdaCodePath.toString(), ENV);
- tags.entrySet().stream()
- .filter(tag -> Objects.nonNull(tag.getValue()))
- .forEach(tag -> Tags.of(app).add(tag.getKey(), tag.getValue()));
+ tags.entrySet().stream()
+ .filter(tag -> Objects.nonNull(tag.getValue()))
+ .forEach(tag -> Tags.of(app).add(tag.getKey(), tag.getValue()));
- template = Template.fromStack(stack);
- }
+ template = Template.fromStack(stack);
+ }
- @AfterAll
- static void cleanup() {
- template = null;
- }
+ @AfterAll
+ static void cleanup() {
+ template = null;
+ }
}
diff --git a/spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TestLambdaUtils.java b/spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TestLambdaUtils.java
similarity index 100%
rename from spring-native-aws-lambda-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TestLambdaUtils.java
rename to spring-native-aws-service-infra/src/test/java/com/coffeebeans/springnativeawslambda/infra/TestLambdaUtils.java
diff --git a/spring-native-aws-lambda-infra/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/spring-native-aws-service-infra/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
similarity index 100%
rename from spring-native-aws-lambda-infra/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
rename to spring-native-aws-service-infra/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
diff --git a/spring-native-aws-lambda-function/pom.xml b/spring-native-aws-service/pom.xml
similarity index 93%
rename from spring-native-aws-lambda-function/pom.xml
rename to spring-native-aws-service/pom.xml
index 0c4b28c..32e65c8 100644
--- a/spring-native-aws-lambda-function/pom.xml
+++ b/spring-native-aws-service/pom.xml
@@ -28,7 +28,7 @@
${revision}
- spring-native-aws-lambda-function
+ spring-native-aws-service
${project.artifactId}
jar
Demo project for Spring cloud function with graalvm native image
@@ -43,7 +43,9 @@
2023.0.0
-
+
+ 21
+ 21
@@ -100,6 +102,11 @@
org.springframework.boot
spring-boot-starter-validation
+
+
+ io.awspring.cloud
+ spring-cloud-aws-starter-dynamodb
+
@@ -170,6 +177,9 @@
--strict-image-heap
-H:+ReportExceptionStackTraces
+
+ true
+
diff --git a/spring-native-aws-lambda-function/src/assembly/native.xml b/spring-native-aws-service/src/assembly/native.xml
similarity index 96%
rename from spring-native-aws-lambda-function/src/assembly/native.xml
rename to spring-native-aws-service/src/assembly/native.xml
index f04e937..91337ea 100644
--- a/spring-native-aws-lambda-function/src/assembly/native.xml
+++ b/spring-native-aws-service/src/assembly/native.xml
@@ -40,7 +40,7 @@
true
0775
- spring-native-aws-lambda-function
+ spring-native-aws-service
diff --git a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/Application.java b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/Application.java
similarity index 100%
rename from spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/Application.java
rename to spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/Application.java
diff --git a/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/DynamoDbConfig.java b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/DynamoDbConfig.java
new file mode 100644
index 0000000..38fa1ca
--- /dev/null
+++ b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/DynamoDbConfig.java
@@ -0,0 +1,21 @@
+package com.coffeebeans.springnativeawslambda;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.validation.annotation.Validated;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+
+@Validated
+@AutoConfiguration
+@ConditionalOnProperty(name = "spring.cloud.aws.dynamodb.enabled", havingValue = "true", matchIfMissing = true)
+public class DynamoDbConfig {
+
+ @Bean
+ public DynamoDbEnhancedClient enhancedClient(final DynamoDbClient dynamoDbClient) {
+ return DynamoDbEnhancedClient.builder()
+ .dynamoDbClient(dynamoDbClient)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/LambdaExceptionHandler.java b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/LambdaExceptionHandler.java
similarity index 72%
rename from spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/LambdaExceptionHandler.java
rename to spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/LambdaExceptionHandler.java
index e3764be..f42423d 100644
--- a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/LambdaExceptionHandler.java
+++ b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/LambdaExceptionHandler.java
@@ -5,9 +5,12 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import java.util.Map;
+import java.util.Set;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.server.MethodNotAllowedException;
@Slf4j
@RestControllerAdvice
@@ -27,6 +30,25 @@ public APIGatewayProxyResponseEvent handleException(
+ " }");
}
+ @ExceptionHandler(MethodNotAllowedException.class)
+ public APIGatewayProxyResponseEvent handleException(
+ final MethodNotAllowedException e,
+ final APIGatewayProxyRequestEvent request) {
+ log.error("Error processing request: {}", e.getMessage());
+
+ final Set supportedMethods = e.getSupportedMethods();
+
+ return new APIGatewayProxyResponseEvent()
+ .withStatusCode(405)
+ .withHeaders(Map.of("X-Requested-Id", request.getRequestContext().getRequestId()))
+ .withBody(
+ ("{\n"
+ + "\"message\": \"Method not allowed. Supported methods [%s]\",\n"
+ + " \"errorCode\": \"METHOD_NOT_ALLOWED\n"
+ + " }").formatted(
+ supportedMethods));
+ }
+
@ExceptionHandler(InvalidDefinitionException.class)
public APIGatewayProxyResponseEvent handleException(
final InvalidDefinitionException e,
diff --git a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/ReflectionRuntimeHints.java b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/ReflectionRuntimeHints.java
similarity index 62%
rename from spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/ReflectionRuntimeHints.java
rename to spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/ReflectionRuntimeHints.java
index 765b03f..b9e3771 100644
--- a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/ReflectionRuntimeHints.java
+++ b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/ReflectionRuntimeHints.java
@@ -18,11 +18,9 @@
package com.coffeebeans.springnativeawslambda;
-import java.util.List;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
-import com.coffeebeans.springnativeawslambda.model.Request;
-import com.coffeebeans.springnativeawslambda.model.Response;
+import com.coffeebeans.springnativeawslambda.model.Secret;
import org.joda.time.DateTime;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
@@ -30,19 +28,21 @@
import org.springframework.aot.hint.TypeReference;
import org.springframework.lang.Nullable;
+import java.util.List;
+
public class ReflectionRuntimeHints implements RuntimeHintsRegistrar {
- @Override
- public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
- final List typeReferences = List.of(
- TypeReference.of(DateTime.class),
- TypeReference.of(Response.class),
- TypeReference.of(Request.class),
- TypeReference.of(APIGatewayProxyResponseEvent.class),
- TypeReference.of(APIGatewayProxyRequestEvent.class),
- TypeReference.of(APIGatewayProxyRequestEvent.ProxyRequestContext.class),
- TypeReference.of(APIGatewayProxyRequestEvent.RequestIdentity.class));
+ @Override
+ public void registerHints(final RuntimeHints hints, @Nullable final ClassLoader classLoader) {
+ final List typeReferences = List.of(
+ TypeReference.of(DateTime.class),
+ TypeReference.of(Secret.class),
+ TypeReference.of(APIGatewayProxyResponseEvent.class),
+ TypeReference.of(APIGatewayProxyRequestEvent.class),
+ TypeReference.of(APIGatewayProxyRequestEvent.ProxyRequestContext.class),
+ TypeReference.of(APIGatewayProxyRequestEvent.RequestIdentity.class)
+ );
- hints.reflection().registerTypes(typeReferences, builder -> builder.withMembers(MemberCategory.values()));
- }
+ hints.reflection().registerTypes(typeReferences, builder -> builder.withMembers(MemberCategory.values()));
+ }
}
diff --git a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/ResourcesRuntimeHints.java b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/ResourcesRuntimeHints.java
similarity index 91%
rename from spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/ResourcesRuntimeHints.java
rename to spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/ResourcesRuntimeHints.java
index 9ddaa72..ba11981 100644
--- a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/ResourcesRuntimeHints.java
+++ b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/ResourcesRuntimeHints.java
@@ -26,5 +26,6 @@ public class ResourcesRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(final RuntimeHints hints, final ClassLoader classLoader) {
hints.resources().registerPattern("com/amazonaws/lambda/thirdparty/org/joda/time/tz/*");
+ hints.resources().registerPattern("io/awspring/cloud/core/SpringCloudClientConfiguration.properties");
}
}
\ No newline at end of file
diff --git a/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/entity/Secret.java b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/entity/Secret.java
new file mode 100644
index 0000000..5d4a0ee
--- /dev/null
+++ b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/entity/Secret.java
@@ -0,0 +1,38 @@
+package com.coffeebeans.springnativeawslambda.entity;
+
+import java.io.Serializable;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Secret implements Serializable {
+
+ private String env;
+
+ private String costCentre;
+
+ private String applicationName;
+
+ private String partitionKey;
+
+ private Map items;
+
+ public static Secret of(final com.coffeebeans.springnativeawslambda.model.Secret secretModel) {
+ final String env = secretModel.getEnv();
+ final String costCentre = secretModel.getCostCentre();
+ final String applicationName = secretModel.getApplicationName();
+ return Secret.builder()
+ .env(env)
+ .costCentre(costCentre)
+ .applicationName(applicationName)
+ .items(secretModel.getItems())
+ .partitionKey(secretModel.getId())
+ .build();
+ }
+}
diff --git a/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/function/ExampleFunction.java b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/function/ExampleFunction.java
new file mode 100644
index 0000000..755e173
--- /dev/null
+++ b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/function/ExampleFunction.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to Muhammad Hamadto
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.coffeebeans.springnativeawslambda.function;
+
+import static org.springframework.http.HttpMethod.GET;
+import static org.springframework.http.HttpMethod.POST;
+
+import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
+import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
+import com.coffeebeans.springnativeawslambda.model.Secret;
+import com.coffeebeans.springnativeawslambda.repository.SecretRepository;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.MethodInvocationException;
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.server.MethodNotAllowedException;
+
+@Component
+@Slf4j
+@Validated
+public class ExampleFunction implements
+ Function {
+
+ private final ObjectMapper objectMapper;
+
+ private SecretRepository secretRepository;
+
+ public ExampleFunction(
+ @NotNull final SecretRepository secretRepository,
+ @NotNull final ObjectMapper objectMapper
+ ) {
+ this.objectMapper = objectMapper;
+ this.secretRepository = secretRepository;
+
+ }
+
+ /**
+ * Lambda function handler that takes a request and returns a response.
+ *
+ * @param proxyRequestEvent the function argument
+ * @return {@link APIGatewayProxyResponseEvent}
+ * @throws JsonProcessingException
+ */
+ @Override
+ @SneakyThrows(value = JsonProcessingException.class)
+ public APIGatewayProxyResponseEvent apply(final APIGatewayProxyRequestEvent proxyRequestEvent) {
+ log.info("Received a request...'");
+
+ final String httpMethod = proxyRequestEvent.getHttpMethod() == null ? "" : proxyRequestEvent.getHttpMethod().toUpperCase();
+ return switch (httpMethod) {
+ case "POST" -> doPost(proxyRequestEvent);
+ case "GET" -> doGet(proxyRequestEvent);
+ case null, default -> throw new MethodNotAllowedException(httpMethod, List.of(POST, GET));
+ };
+ }
+
+ private APIGatewayProxyResponseEvent doPost(final APIGatewayProxyRequestEvent proxyRequestEvent) throws JsonProcessingException {
+ log.info("POST: Converting request into a response");
+
+ final Secret secret = objectMapper.readValue(proxyRequestEvent.getBody(), Secret.class);
+
+ this.secretRepository.save(com.coffeebeans.springnativeawslambda.entity.Secret.of(secret));
+
+ log.info("POST: Converted request into a response");
+
+ return new APIGatewayProxyResponseEvent()
+ .withStatusCode(200)
+ .withBody(objectMapper.writeValueAsString(secret));
+ }
+
+ private APIGatewayProxyResponseEvent doGet(final APIGatewayProxyRequestEvent proxyRequestEvent) throws JsonProcessingException {
+ log.info("GET: Retrieving response");
+
+ final Map pathParameters = proxyRequestEvent.getPathParameters();
+ final String id = pathParameters.get("proxy");
+
+ final com.coffeebeans.springnativeawslambda.entity.Secret secretEntity = this.secretRepository.findById(id);
+
+ final Secret secret = Secret.of(secretEntity);
+
+ if (secret == null) {
+ return new APIGatewayProxyResponseEvent()
+ .withStatusCode(404)
+ .withBody("Entity not found");
+ }
+
+ log.info("GET: response retrieved successfully");
+ return new APIGatewayProxyResponseEvent()
+ .withStatusCode(200)
+ .withBody(objectMapper.writeValueAsString(secret));
+ }
+}
\ No newline at end of file
diff --git a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/model/Response.java b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/model/Secret.java
similarity index 50%
rename from spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/model/Response.java
rename to spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/model/Secret.java
index 438e804..9d16fa7 100644
--- a/spring-native-aws-lambda-function/src/main/java/com/coffeebeans/springnativeawslambda/model/Response.java
+++ b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/model/Secret.java
@@ -21,7 +21,8 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.NotEmpty;
+import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -31,13 +32,41 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
-public class Response {
-
+public class Secret {
@NotBlank
@JsonProperty(access = Access.READ_ONLY)
- private String name;
+ private String id;
- @NotNull
- @JsonProperty(access = Access.READ_ONLY)
- private boolean saved;
+ @NotBlank
+ private String env;
+
+ @NotBlank
+ private String costCentre;
+
+ @NotBlank
+ private String applicationName;
+
+ @NotEmpty
+ private Map items;
+
+ public String getId() {
+ final String env = this.env.toLowerCase().replaceAll("[^a-zA-Z0-9]", "");
+ final String costCentre = this.costCentre.toLowerCase().replaceAll("[^a-zA-Z0-9]", "");
+ final String applicationName = this.applicationName.toLowerCase().replaceAll("[^a-zA-Z0-9]", "");
+
+ return "%s-%s-%s".formatted(env, costCentre, applicationName);
+ }
+
+ public static Secret of(final com.coffeebeans.springnativeawslambda.entity.Secret secretEntity) {
+ final String env = secretEntity.getEnv();
+ final String costCentre = secretEntity.getCostCentre();
+ final String applicationName = secretEntity.getApplicationName();
+ return Secret.builder()
+ .env(env)
+ .costCentre(costCentre)
+ .applicationName(applicationName)
+ .items(secretEntity.getItems())
+ .id(secretEntity.getPartitionKey())
+ .build();
+ }
}
diff --git a/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/repository/SecretRepository.java b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/repository/SecretRepository.java
new file mode 100644
index 0000000..eb4b2e9
--- /dev/null
+++ b/spring-native-aws-service/src/main/java/com/coffeebeans/springnativeawslambda/repository/SecretRepository.java
@@ -0,0 +1,56 @@
+package com.coffeebeans.springnativeawslambda.repository;
+
+import com.coffeebeans.springnativeawslambda.entity.Secret;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.stereotype.Repository;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
+import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
+import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
+import software.amazon.awssdk.enhanced.dynamodb.Key;
+import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTags;
+import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableSchema;
+
+@Repository
+public class SecretRepository {
+
+ public static final TableSchema TABLE_SCHEMA = StaticTableSchema.builder(Secret.class)
+ .newItemSupplier(Secret::new)
+ .addAttribute(String.class, a -> a.name("id")
+ .getter(Secret::getPartitionKey)
+ .setter(Secret::setPartitionKey)
+ .tags(StaticAttributeTags.primaryPartitionKey())
+ )
+ .addAttribute(String.class, a -> a.name("env")
+ .getter(Secret::getEnv)
+ .setter(Secret::setEnv)
+ )
+ .addAttribute(String.class, a -> a.name("costCentre")
+ .getter(Secret::getCostCentre)
+ .setter(Secret::setCostCentre)
+ )
+ .addAttribute(String.class, a -> a.name("applicationName")
+ .getter(Secret::getApplicationName)
+ .setter(Secret::setApplicationName)
+ )
+ .addAttribute(EnhancedType.mapOf(String.class, String.class), a -> a.name("variables")
+ .getter(Secret::getItems)
+ .setter(Secret::setItems)
+ )
+ .build();
+
+ private final DynamoDbTable mappedTable;
+
+ public SecretRepository(@NotNull final DynamoDbEnhancedClient enhancedClient) {
+ this.mappedTable = enhancedClient.table("secrets", TABLE_SCHEMA);
+ }
+
+ public Secret findById(String id) {
+ final Key key = Key.builder().partitionValue(id).build();
+ return this.mappedTable.getItem(r -> r.key(key));
+ }
+
+ public void save(final Secret secret) {
+ this.mappedTable.putItem(secret);
+ }
+}
diff --git a/spring-native-aws-lambda-function/src/main/resources/application-local.yml b/spring-native-aws-service/src/main/resources/application-local.yml
similarity index 87%
rename from spring-native-aws-lambda-function/src/main/resources/application-local.yml
rename to spring-native-aws-service/src/main/resources/application-local.yml
index 32d68c5..c76e11d 100644
--- a/spring-native-aws-lambda-function/src/main/resources/application-local.yml
+++ b/spring-native-aws-service/src/main/resources/application-local.yml
@@ -15,4 +15,8 @@
spring:
main:
- web-application-type: servlet
\ No newline at end of file
+ web-application-type: servlet
+ cloud:
+ aws:
+ dynamodb:
+ endpoint: http://localhost:8000
\ No newline at end of file
diff --git a/spring-native-aws-lambda-function/src/main/resources/application.yml b/spring-native-aws-service/src/main/resources/application.yml
similarity index 90%
rename from spring-native-aws-lambda-function/src/main/resources/application.yml
rename to spring-native-aws-service/src/main/resources/application.yml
index ec49d88..2562a8a 100644
--- a/spring-native-aws-lambda-function/src/main/resources/application.yml
+++ b/spring-native-aws-service/src/main/resources/application.yml
@@ -32,4 +32,8 @@ spring:
enabled: false
debug: false
definition: exampleFunction
-debug: false
\ No newline at end of file
+ aws:
+ dynamodb:
+ enabled: true
+ endpoint: https://dynamodb.ap-southeast-2.amazonaws.com
+debug: false
diff --git a/spring-native-aws-lambda-function/src/shell/native/bootstrap b/spring-native-aws-service/src/shell/native/bootstrap
similarity index 95%
rename from spring-native-aws-lambda-function/src/shell/native/bootstrap
rename to spring-native-aws-service/src/shell/native/bootstrap
index 33d505c..ff8d3b6 100755
--- a/spring-native-aws-lambda-function/src/shell/native/bootstrap
+++ b/spring-native-aws-service/src/shell/native/bootstrap
@@ -21,4 +21,4 @@ set -euo pipefail
cd ${LAMBDA_TASK_ROOT:-.}
-./spring-native-aws-lambda-function
\ No newline at end of file
+./spring-native-aws-service
\ No newline at end of file
diff --git a/spring-native-aws-lambda-function/src/test/java/com/coffeebeans/springnativeawslambda/ApplicationIT.java b/spring-native-aws-service/src/test/java/com/coffeebeans/springnativeawslambda/ApplicationIT.java
similarity index 82%
rename from spring-native-aws-lambda-function/src/test/java/com/coffeebeans/springnativeawslambda/ApplicationIT.java
rename to spring-native-aws-service/src/test/java/com/coffeebeans/springnativeawslambda/ApplicationIT.java
index 3232430..d70217e 100644
--- a/spring-native-aws-lambda-function/src/test/java/com/coffeebeans/springnativeawslambda/ApplicationIT.java
+++ b/spring-native-aws-service/src/test/java/com/coffeebeans/springnativeawslambda/ApplicationIT.java
@@ -73,4 +73,20 @@ void should_return_400() throws JsonProcessingException {
.isEqualTo(objectMapper.writeValueAsString(response));
}
+
+ @Test
+ void should_return_405() throws JsonProcessingException {
+
+ final APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent()
+ .withBody("\"name\":\"Coffeebeans\"}")
+ .withHttpMethod("PUT");
+
+ final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent()
+ .withStatusCode(405)
+ .withBody("{\"message\": \"Method not allowed. Supported methods [POST, GET]\",\"errorCode\": \"METHOD_NOT_ALLOWED\"}");
+
+ assertThat(aws.exchange(objectMapper.writeValueAsString(request)).getPayload())
+ .isEqualTo(objectMapper.writeValueAsString(response));
+
+ }
}
\ No newline at end of file
diff --git a/spring-native-aws-service/src/test/java/com/coffeebeans/springnativeawslambda/function/ExampleFunctionTest.java b/spring-native-aws-service/src/test/java/com/coffeebeans/springnativeawslambda/function/ExampleFunctionTest.java
new file mode 100644
index 0000000..9199915
--- /dev/null
+++ b/spring-native-aws-service/src/test/java/com/coffeebeans/springnativeawslambda/function/ExampleFunctionTest.java
@@ -0,0 +1,72 @@
+///*
+// * Licensed to Muhammad Hamadto
+// *
+// * Licensed under the Apache License, Version 2.0 (the "License");
+// * you may not use this file except in compliance with the License.
+// * You may obtain a copy of the License at
+// * http://www.apache.org/licenses/LICENSE-2.0
+// *
+// * See the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// *
+// * Unless required by applicable law or agreed to in writing, software
+// * distributed under the License is distributed on an "AS IS" BASIS,
+// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// * See the License for the specific language governing permissions and
+// * limitations under the License.
+// *
+// */
+//
+//package com.coffeebeans.springnativeawslambda.function;
+//
+//import static org.assertj.core.api.Assertions.assertThat;
+//import static org.assertj.core.api.Assertions.assertThatThrownBy;
+//
+//import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
+//import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
+//import com.coffeebeans.springnativeawslambda.function.ExampleFunction;
+//import com.fasterxml.jackson.core.JsonProcessingException;
+//import com.fasterxml.jackson.databind.ObjectMapper;
+//import org.junit.jupiter.api.BeforeEach;
+//import org.junit.jupiter.api.Test;
+//import org.mockito.Spy;
+//
+//class ExampleFunctionTest {
+//
+// private ExampleFunction exampleFunction;
+//
+// @Spy
+// private ObjectMapper objectMapper = new ObjectMapper();
+//
+// @BeforeEach
+// void setUp() {
+// exampleFunction = new ExampleFunction(objectMapper);
+// }
+//
+// @Test
+// void should_return_APIGatewayProxyResponseEvent() {
+// final String requestBody = "{\"name\":\"Coffeebeans\"}";
+// final String responseBody = "{\"name\":\"Coffeebeans\",\"saved\":true}";
+//
+// final APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent()
+// .withBody(requestBody);
+//
+// final APIGatewayProxyResponseEvent apiGatewayProxyResponseEvent = new APIGatewayProxyResponseEvent()
+// .withStatusCode(200)
+// .withBody(responseBody);
+//
+// final APIGatewayProxyResponseEvent actual = exampleFunction.apply(request);
+//
+// assertThat(actual)
+// .isEqualTo(apiGatewayProxyResponseEvent);
+// }
+//
+// @Test
+// void should_throw_JsonProcessingException() {
+//
+// final APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent()
+// .withBody("Coffeebeans");
+//
+// assertThatThrownBy(() -> exampleFunction.apply(request))
+// .isInstanceOf(JsonProcessingException.class);
+// }
+//}
\ No newline at end of file