Skip to content

Renaming #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -15,5 +15,5 @@ README.md

settings-spring.xml

spring-native-aws-lambda-infra
spring-native-aws-service-infra
docker-compose.yml
8 changes: 6 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -51,15 +51,19 @@ 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 }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
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:
2 changes: 1 addition & 1 deletion NOTICE
Original file line number Diff line number Diff line change
@@ -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:
2 changes: 1 addition & 1 deletion PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -7,6 +7,6 @@

<!--- Please include the following in your Pull Request when applicable: -->

- [ ] 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
247 changes: 180 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
@@ -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,21 +43,26 @@ $ ./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`

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.
```

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.

5 changes: 4 additions & 1 deletion cdk.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
12 changes: 7 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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" &&
38 changes: 30 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -26,16 +26,15 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
<relativePath />
<relativePath/>
</parent>

<groupId>com.coffeebeans</groupId>
<artifactId>spring-native-aws-lambda</artifactId>
<name>${project.artifactId}</name>
<version>${revision}</version>
<packaging>pom</packaging>
<description>Demo project for Spring cloud function with graalvm native image deployed with cdk
</description>
<description>Demo project for Spring cloud function with graalvm native image deployed with cdk</description>

<developers>
<developer>
@@ -79,26 +78,42 @@
</properties>

<modules>
<module>spring-native-aws-lambda-function</module>
<module>spring-native-aws-lambda-infra</module>
<module>spring-native-aws-service</module>
<module>spring-native-aws-service-infra</module>
</modules>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>3.1.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- SPRING-NATIVE-AWS-LAMBDA -->
<dependency>
<groupId>com.coffeebeans</groupId>
<artifactId>spring-native-aws-lambda-function</artifactId>
<artifactId>spring-native-aws-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.coffeebeans</groupId>
<artifactId>spring-native-aws-lambda-infra</artifactId>
<artifactId>spring-native-aws-service-infra</artifactId>
<version>${project.version}</version>
</dependency>

<!-- SPRING-NATIVE-AWS-LAMBDA ENDS -->

<dependency>
<groupId>io.sandpipers</groupId>
<artifactId>sandpipers-cdk-bom</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<!-- AWS -->
<dependency>
<groupId>com.amazonaws</groupId>
@@ -187,7 +202,7 @@
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>aws-cdk-lib</artifactId>
<version>2.116.1</version>
<version>2.128.0</version>
</dependency>
<!-- AWS CDK ENDS -->

@@ -213,6 +228,13 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@
<version>${revision}</version>
</parent>

<artifactId>spring-native-aws-lambda-infra</artifactId>
<artifactId>spring-native-aws-service-infra</artifactId>
<name>${project.artifactId}</name>
<packaging>jar</packaging>
<description>Infrastructure as Code for deploying a Spring cloud function demo project with
@@ -55,6 +55,11 @@
</dependency>
<!-- AWS CDK ENDS -->

<dependency>
<groupId>io.sandpipers</groupId>
<artifactId>sandpipers-cdk-core</artifactId>
</dependency>

<!-- UTILS -->
<dependency>
<groupId>org.projectlombok</groupId>
@@ -94,8 +99,8 @@

<!-- TEST -->
<dependency>
<groupId>cloud.pianola</groupId>
<artifactId>cdk-fluent-assertions</artifactId>
<groupId>io.sandpipers</groupId>
<artifactId>sandpipers-cdk-assertions</artifactId>
<scope>test</scope>
</dependency>

@@ -111,6 +116,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Original file line number Diff line number Diff line change
@@ -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<String, String> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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";
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<IManagedPolicy> 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<String, String> 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"));
}
}
Original file line number Diff line number Diff line change
@@ -24,14 +24,20 @@
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;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class TagUtils {

public static Map<String, String> 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
);
}
}
Original file line number Diff line number Diff line change
@@ -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");
// }
//}
Original file line number Diff line number Diff line change
@@ -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<String, String> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
}
Loading