Compare commits
12 Commits
artur/test
...
18-validat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97bf254d85 | ||
|
|
381fe85e39 | ||
|
|
a418f21ff1 | ||
|
|
735d80d72f | ||
|
|
3eb97d81ec | ||
|
|
27e1428488 | ||
|
|
7b63198a08 | ||
|
|
45c947ddc1 | ||
|
|
e41458b7cd | ||
|
|
03ab12c64b | ||
|
|
f183c92661 | ||
|
|
109dacd121 |
32
aws/aws-rds-hello-world/.gitignore
vendored
Normal file
32
aws/aws-rds-hello-world/.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
HELP.md
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**
|
||||
!**/src/test/**
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
5
aws/aws-rds-hello-world/Dockerfile
Normal file
5
aws/aws-rds-hello-world/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM openjdk:8-jdk-alpine
|
||||
ARG JAR_FILE=build/libs/*.jar
|
||||
COPY ${JAR_FILE} app.jar
|
||||
ENTRYPOINT ["java","-jar","/app.jar"]
|
||||
EXPOSE 8080
|
||||
25
aws/aws-rds-hello-world/README.md
Normal file
25
aws/aws-rds-hello-world/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# RDS Hello World Application
|
||||
|
||||
This is a simple Spring Boot application which requires access to a PostgreSQL database.
|
||||
|
||||
The application has a single endpoint `/hello` which prints out if the database connection was successful.
|
||||
|
||||
Get it in a Docker image via `docker pull reflectoring/aws-rds-hello-world`.
|
||||
|
||||
Use the image instead of your real application to test AWS CloudFormation stacks which need access to a database.
|
||||
|
||||
## Testing AWS RDS connectivity with this application
|
||||
|
||||
1. Create an RDS PostgreSQL database with the AWS console.
|
||||
2. Note the endpoint of your RDS database in the AWS console.
|
||||
3. Deploy the Docker container `reflectoring/aws-rds-hello-world` into AWS instead of your real application (this could be via a CloudFormation stack, manually, or however you are deploying your app).
|
||||
4. Configure your deployment in a way that Docker will pass the coordinates to your RDS database as environment variables, equivalent to this command:
|
||||
```
|
||||
docker run \
|
||||
-e SPRING_DATASOURCE_URL=':'<RDS-ENDPOINT>:5432/postgres \
|
||||
-e SPRING_DATASOURCE_USERNAME=<USERNAME> \
|
||||
-e SPRING_DATASOURCE_PASSWORD=<PASSWORD> \
|
||||
-p 8080:8080 reflectoring/aws-rds-hello-world
|
||||
```
|
||||
5. If the Spring Boot application can connect to the database, it will start up sucessfully and serve a message on the endpoint `/hello`.
|
||||
|
||||
31
aws/aws-rds-hello-world/build.gradle
Normal file
31
aws/aws-rds-hello-world/build.gradle
Normal file
@@ -0,0 +1,31 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '2.2.4.RELEASE'
|
||||
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group = 'io.reflectoring'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = '1.8'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
|
||||
// database
|
||||
implementation 'org.flywaydb:flyway-core'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
testRuntimeOnly 'org.postgresql:postgresql'
|
||||
|
||||
testImplementation('org.springframework.boot:spring-boot-starter-test') {
|
||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
18
aws/aws-rds-hello-world/docker-compose.yml
Normal file
18
aws/aws-rds-hello-world/docker-compose.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
|
||||
postgres:
|
||||
container_name: "rds-hello-world"
|
||||
image: postgres
|
||||
volumes:
|
||||
- rds-hello-world:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
- POSTGRES_USER=hello
|
||||
- POSTGRES_PASSWORD=hello
|
||||
|
||||
volumes:
|
||||
rds-hello-world:
|
||||
driver: local
|
||||
BIN
aws/aws-rds-hello-world/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
aws/aws-rds-hello-world/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
aws/aws-rds-hello-world/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
aws/aws-rds-hello-world/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
172
aws/aws-rds-hello-world/gradlew
vendored
Executable file
172
aws/aws-rds-hello-world/gradlew
vendored
Executable file
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
84
aws/aws-rds-hello-world/gradlew.bat
vendored
Executable file
84
aws/aws-rds-hello-world/gradlew.bat
vendored
Executable file
@@ -0,0 +1,84 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
1
aws/aws-rds-hello-world/settings.gradle
Normal file
1
aws/aws-rds-hello-world/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'aws-rds-hello-world'
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.reflectoring.awshelloworld;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class AwsHelloWorldApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AwsHelloWorldApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.reflectoring.awshelloworld;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
class HelloWorldController {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
HelloWorldController(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/hello")
|
||||
String helloWorld(){
|
||||
|
||||
Iterable<User> users = userRepository.findAll();
|
||||
|
||||
return "Hello AWS! Successfully connected to the database!";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.reflectoring.awshelloworld;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
@Table("hello_user")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
public User(Long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public User() {
|
||||
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.reflectoring.awshelloworld;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface UserRepository extends CrudRepository<User, Long> {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/hello
|
||||
username: hello
|
||||
password: hello
|
||||
@@ -0,0 +1,5 @@
|
||||
create table hello_user (
|
||||
id varchar(36) not null unique,
|
||||
name varchar(100) not null,
|
||||
primary key(id)
|
||||
);
|
||||
8
aws/cloudformation/ecs-in-two-public-subnets/README.md
Normal file
8
aws/cloudformation/ecs-in-two-public-subnets/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Overview
|
||||
|
||||

|
||||
|
||||
# Companion Blog Post
|
||||
|
||||
[The AWS Journey Part 2: Deploying a Docker image from the Command Line with CloudFormation](https://reflectoring.io/aws-cloudformation-deploy-docker-image/)
|
||||
|
||||
@@ -76,6 +76,12 @@ Resources:
|
||||
!Join [':', [!Ref 'StackName', 'PublicListener']]
|
||||
Priority: 1
|
||||
|
||||
LogGroup:
|
||||
Type: AWS::Logs::LogGroup
|
||||
Properties:
|
||||
LogGroupName: !Ref 'ServiceName'
|
||||
RetentionInDays: 1
|
||||
|
||||
TaskDefinition:
|
||||
Type: AWS::ECS::TaskDefinition
|
||||
Properties:
|
||||
|
||||
7
aws/cloudformation/rds-in-private-subnet/README.md
Normal file
7
aws/cloudformation/rds-in-private-subnet/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Overview
|
||||
|
||||

|
||||
|
||||
# Companion Blog Post
|
||||
|
||||
TO DO
|
||||
88
aws/cloudformation/rds-in-private-subnet/database.yml
Normal file
88
aws/cloudformation/rds-in-private-subnet/database.yml
Normal file
@@ -0,0 +1,88 @@
|
||||
AWSTemplateFormatVersion: '2010-09-09'
|
||||
Description: A stack that creates an RDS instance and places it into two subnets
|
||||
Parameters:
|
||||
NetworkStackName:
|
||||
Type: String
|
||||
Description: The name of the networking stack that this stack will build upon.
|
||||
DBInstanceClass:
|
||||
Type: String
|
||||
Description: The ID of the second subnet to place the RDS instance into.
|
||||
Default: 'db.t2.micro'
|
||||
DBName:
|
||||
Type: String
|
||||
Description: The name of the database that is created within the PostgreSQL instance.
|
||||
DBUsername:
|
||||
Type: String
|
||||
Description: The master user name for the PostgreSQL instance.
|
||||
Resources:
|
||||
|
||||
Secret:
|
||||
Type: "AWS::SecretsManager::Secret"
|
||||
Properties:
|
||||
Name: !Ref 'DBUsername'
|
||||
GenerateSecretString:
|
||||
# This will generate a JSON object with the keys "username" and password.
|
||||
SecretStringTemplate: !Join ['', ['{"username": "', !Ref 'DBUsername' ,'"}']]
|
||||
GenerateStringKey: "password"
|
||||
PasswordLength: 32
|
||||
ExcludeCharacters: '"@/\'
|
||||
|
||||
DBSubnetGroup:
|
||||
Type: AWS::RDS::DBSubnetGroup
|
||||
Properties:
|
||||
DBSubnetGroupDescription: Subnet group for the RDS instance
|
||||
DBSubnetGroupName: DBSubnetGroup
|
||||
SubnetIds:
|
||||
- Fn::ImportValue:
|
||||
!Join [':', [!Ref 'NetworkStackName', 'PrivateSubnetOne']]
|
||||
- Fn::ImportValue:
|
||||
!Join [':', [!Ref 'NetworkStackName', 'PrivateSubnetTwo']]
|
||||
|
||||
PostgresInstance:
|
||||
Type: AWS::RDS::DBInstance
|
||||
Properties:
|
||||
AllocatedStorage: 20
|
||||
AvailabilityZone:
|
||||
Fn::Select:
|
||||
- 0
|
||||
- Fn::GetAZs: {Ref: 'AWS::Region'}
|
||||
DBInstanceClass: !Ref 'DBInstanceClass'
|
||||
DBName: !Ref 'DBName'
|
||||
DBSubnetGroupName: !Ref 'DBSubnetGroup'
|
||||
Engine: postgres
|
||||
EngineVersion: 11.5
|
||||
MasterUsername: !Ref 'DBUsername'
|
||||
MasterUserPassword: !Join ['', ['{{resolve:secretsmanager:', !Ref Secret, ':SecretString:password}}' ]]
|
||||
PubliclyAccessible: false
|
||||
VPCSecurityGroups:
|
||||
- Fn::ImportValue:
|
||||
!Join [':', [!Ref 'NetworkStackName', 'DBSecurityGroupId']]
|
||||
|
||||
SecretRDSInstanceAttachment:
|
||||
Type: "AWS::SecretsManager::SecretTargetAttachment"
|
||||
Properties:
|
||||
SecretId: !Ref Secret
|
||||
TargetId: !Ref PostgresInstance
|
||||
TargetType: AWS::RDS::DBInstance
|
||||
|
||||
Outputs:
|
||||
EndpointAddress:
|
||||
Description: Address of the RDS endpoint.
|
||||
Value: !GetAtt 'PostgresInstance.Endpoint.Address'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'EndpointAddress' ] ]
|
||||
EndpointPort:
|
||||
Description: Port of the RDS endpoint.
|
||||
Value: !GetAtt 'PostgresInstance.Endpoint.Port'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'EndpointPort' ] ]
|
||||
DBName:
|
||||
Description: The name of the database that is created within the PostgreSQL instance.
|
||||
Value: !Ref DBName
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'DBName' ] ]
|
||||
Secret:
|
||||
Description: Reference to the secret containing the password to the database.
|
||||
Value: !Ref 'Secret'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'Secret' ] ]
|
||||
298
aws/cloudformation/rds-in-private-subnet/network.yml
Normal file
298
aws/cloudformation/rds-in-private-subnet/network.yml
Normal file
@@ -0,0 +1,298 @@
|
||||
AWSTemplateFormatVersion: '2010-09-09'
|
||||
Description: A network stack for deploying containers in AWS ECS.
|
||||
This stack creates a VPC with two public subnets and a loadbalancer to balance traffic between those subnets.
|
||||
Derived from a template at https://github.com/nathanpeck/aws-cloudformation-fargate.
|
||||
Resources:
|
||||
|
||||
VPC:
|
||||
Type: AWS::EC2::VPC
|
||||
Properties:
|
||||
CidrBlock: '10.0.0.0/16'
|
||||
|
||||
PublicSubnetOne:
|
||||
Type: AWS::EC2::Subnet
|
||||
Properties:
|
||||
AvailabilityZone:
|
||||
Fn::Select:
|
||||
- 0
|
||||
- Fn::GetAZs: {Ref: 'AWS::Region'}
|
||||
VpcId: !Ref 'VPC'
|
||||
CidrBlock: '10.0.1.0/24'
|
||||
MapPublicIpOnLaunch: true
|
||||
|
||||
PublicSubnetTwo:
|
||||
Type: AWS::EC2::Subnet
|
||||
Properties:
|
||||
AvailabilityZone:
|
||||
Fn::Select:
|
||||
- 1
|
||||
- Fn::GetAZs: {Ref: 'AWS::Region'}
|
||||
VpcId: !Ref 'VPC'
|
||||
CidrBlock: '10.0.2.0/24'
|
||||
MapPublicIpOnLaunch: true
|
||||
|
||||
InternetGateway:
|
||||
Type: AWS::EC2::InternetGateway
|
||||
|
||||
GatewayAttachement:
|
||||
Type: AWS::EC2::VPCGatewayAttachment
|
||||
Properties:
|
||||
VpcId: !Ref 'VPC'
|
||||
InternetGatewayId: !Ref 'InternetGateway'
|
||||
|
||||
PublicRouteTable:
|
||||
Type: AWS::EC2::RouteTable
|
||||
Properties:
|
||||
VpcId: !Ref 'VPC'
|
||||
|
||||
PublicSubnetOneRouteTableAssociation:
|
||||
Type: AWS::EC2::SubnetRouteTableAssociation
|
||||
Properties:
|
||||
SubnetId: !Ref PublicSubnetOne
|
||||
RouteTableId: !Ref PublicRouteTable
|
||||
|
||||
PublicSubnetTwoRouteTableAssociation:
|
||||
Type: AWS::EC2::SubnetRouteTableAssociation
|
||||
Properties:
|
||||
SubnetId: !Ref PublicSubnetTwo
|
||||
RouteTableId: !Ref PublicRouteTable
|
||||
|
||||
PublicRoute:
|
||||
Type: AWS::EC2::Route
|
||||
DependsOn: GatewayAttachement
|
||||
Properties:
|
||||
RouteTableId: !Ref 'PublicRouteTable'
|
||||
DestinationCidrBlock: '0.0.0.0/0'
|
||||
GatewayId: !Ref 'InternetGateway'
|
||||
|
||||
PublicLoadBalancerSecurityGroup:
|
||||
Type: AWS::EC2::SecurityGroup
|
||||
Properties:
|
||||
GroupDescription: Access to the public facing load balancer
|
||||
VpcId: !Ref 'VPC'
|
||||
SecurityGroupIngress:
|
||||
# Allow access to ALB from anywhere on the internet
|
||||
- CidrIp: 0.0.0.0/0
|
||||
IpProtocol: -1
|
||||
|
||||
PublicLoadBalancer:
|
||||
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
|
||||
Properties:
|
||||
Scheme: internet-facing
|
||||
Subnets:
|
||||
# The load balancer is placed into the public subnets, so that traffic
|
||||
# from the internet can reach the load balancer directly via the internet gateway
|
||||
- !Ref PublicSubnetOne
|
||||
- !Ref PublicSubnetTwo
|
||||
SecurityGroups: [!Ref 'PublicLoadBalancerSecurityGroup']
|
||||
|
||||
DummyTargetGroupPublic:
|
||||
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
||||
Properties:
|
||||
HealthCheckIntervalSeconds: 6
|
||||
HealthCheckPath: /
|
||||
HealthCheckProtocol: HTTP
|
||||
HealthCheckTimeoutSeconds: 5
|
||||
HealthyThresholdCount: 2
|
||||
Name: "no-op"
|
||||
Port: 80
|
||||
Protocol: HTTP
|
||||
UnhealthyThresholdCount: 2
|
||||
VpcId: !Ref 'VPC'
|
||||
|
||||
PublicLoadBalancerListener:
|
||||
Type: AWS::ElasticLoadBalancingV2::Listener
|
||||
DependsOn:
|
||||
- PublicLoadBalancer
|
||||
Properties:
|
||||
DefaultActions:
|
||||
- TargetGroupArn: !Ref 'DummyTargetGroupPublic'
|
||||
Type: 'forward'
|
||||
LoadBalancerArn: !Ref 'PublicLoadBalancer'
|
||||
Port: 80
|
||||
Protocol: HTTP
|
||||
|
||||
ECSCluster:
|
||||
Type: AWS::ECS::Cluster
|
||||
|
||||
ECSSecurityGroup:
|
||||
Type: AWS::EC2::SecurityGroup
|
||||
Properties:
|
||||
GroupDescription: Access to the ECS containers
|
||||
VpcId: !Ref 'VPC'
|
||||
|
||||
ECSSecurityGroupIngressFromPublicALB:
|
||||
Type: AWS::EC2::SecurityGroupIngress
|
||||
Properties:
|
||||
Description: Ingress from the public ALB
|
||||
GroupId: !Ref 'ECSSecurityGroup'
|
||||
IpProtocol: -1
|
||||
SourceSecurityGroupId: !Ref 'PublicLoadBalancerSecurityGroup'
|
||||
|
||||
ECSSecurityGroupIngressFromSelf:
|
||||
Type: AWS::EC2::SecurityGroupIngress
|
||||
Properties:
|
||||
Description: Ingress from other containers in the same security group
|
||||
GroupId: !Ref 'ECSSecurityGroup'
|
||||
IpProtocol: -1
|
||||
SourceSecurityGroupId: !Ref 'ECSSecurityGroup'
|
||||
|
||||
ECSRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: [ecs.amazonaws.com]
|
||||
Action: ['sts:AssumeRole']
|
||||
Path: /
|
||||
Policies:
|
||||
- PolicyName: ecs-service
|
||||
PolicyDocument:
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
# Rules which allow ECS to attach network interfaces to instances
|
||||
# on your behalf in order for awsvpc networking mode to work right
|
||||
- 'ec2:AttachNetworkInterface'
|
||||
- 'ec2:CreateNetworkInterface'
|
||||
- 'ec2:CreateNetworkInterfacePermission'
|
||||
- 'ec2:DeleteNetworkInterface'
|
||||
- 'ec2:DeleteNetworkInterfacePermission'
|
||||
- 'ec2:Describe*'
|
||||
- 'ec2:DetachNetworkInterface'
|
||||
|
||||
# Rules which allow ECS to update load balancers on your behalf
|
||||
# with the information sabout how to send traffic to your containers
|
||||
- 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer'
|
||||
- 'elasticloadbalancing:DeregisterTargets'
|
||||
- 'elasticloadbalancing:Describe*'
|
||||
- 'elasticloadbalancing:RegisterInstancesWithLoadBalancer'
|
||||
- 'elasticloadbalancing:RegisterTargets'
|
||||
Resource: '*'
|
||||
|
||||
ECSTaskExecutionRole:
|
||||
Type: AWS::IAM::Role
|
||||
Properties:
|
||||
AssumeRolePolicyDocument:
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Principal:
|
||||
Service: [ecs-tasks.amazonaws.com]
|
||||
Action: ['sts:AssumeRole']
|
||||
Path: /
|
||||
Policies:
|
||||
- PolicyName: AmazonECSTaskExecutionRolePolicy
|
||||
PolicyDocument:
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
Action:
|
||||
# Allow the ECS Tasks to download images from ECR
|
||||
- 'ecr:GetAuthorizationToken'
|
||||
- 'ecr:BatchCheckLayerAvailability'
|
||||
- 'ecr:GetDownloadUrlForLayer'
|
||||
- 'ecr:BatchGetImage'
|
||||
|
||||
# Allow the ECS tasks to upload logs to CloudWatch
|
||||
- 'logs:CreateLogStream'
|
||||
- 'logs:PutLogEvents'
|
||||
Resource: '*'
|
||||
|
||||
PrivateSubnetOne:
|
||||
Type: AWS::EC2::Subnet
|
||||
Properties:
|
||||
AvailabilityZone:
|
||||
Fn::Select:
|
||||
- 0
|
||||
- Fn::GetAZs: {Ref: 'AWS::Region'}
|
||||
VpcId: !Ref 'VPC'
|
||||
CidrBlock: '10.0.101.0/24'
|
||||
MapPublicIpOnLaunch: false
|
||||
|
||||
PrivateSubnetTwo:
|
||||
Type: AWS::EC2::Subnet
|
||||
Properties:
|
||||
AvailabilityZone:
|
||||
Fn::Select:
|
||||
- 1
|
||||
- Fn::GetAZs: {Ref: 'AWS::Region'}
|
||||
VpcId: !Ref 'VPC'
|
||||
CidrBlock: '10.0.102.0/24'
|
||||
MapPublicIpOnLaunch: false
|
||||
|
||||
DBSecurityGroup:
|
||||
Type: AWS::EC2::SecurityGroup
|
||||
Properties:
|
||||
GroupDescription: Access to the RDS instance
|
||||
VpcId: !Ref 'VPC'
|
||||
|
||||
DBSecurityGroupIngressFromECS:
|
||||
Type: AWS::EC2::SecurityGroupIngress
|
||||
Properties:
|
||||
Description: Ingress from the ECS containers to the RDS instance
|
||||
GroupId: !Ref 'DBSecurityGroup'
|
||||
IpProtocol: -1
|
||||
SourceSecurityGroupId: !Ref 'ECSSecurityGroup'
|
||||
|
||||
Outputs:
|
||||
PrivateSubnetOne:
|
||||
Description: Private subnet one
|
||||
Value: !Ref 'PrivateSubnetOne'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrivateSubnetOne' ] ]
|
||||
PrivateSubnetTwo:
|
||||
Description: Private subnet two
|
||||
Value: !Ref 'PrivateSubnetTwo'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PrivateSubnetTwo' ] ]
|
||||
DBSecurityGroupId:
|
||||
Description: ID of the security group that an RDS instance can be placed into.
|
||||
Value: !Ref 'DBSecurityGroup'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'DBSecurityGroupId' ] ]
|
||||
ClusterName:
|
||||
Description: The name of the ECS cluster
|
||||
Value: !Ref 'ECSCluster'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ClusterName' ] ]
|
||||
ExternalUrl:
|
||||
Description: The url of the external load balancer
|
||||
Value: !Join ['', ['http://', !GetAtt 'PublicLoadBalancer.DNSName']]
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ExternalUrl' ] ]
|
||||
ECSRole:
|
||||
Description: The ARN of the ECS role
|
||||
Value: !GetAtt 'ECSRole.Arn'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSRole' ] ]
|
||||
ECSTaskExecutionRole:
|
||||
Description: The ARN of the ECS role
|
||||
Value: !GetAtt 'ECSTaskExecutionRole.Arn'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSTaskExecutionRole' ] ]
|
||||
PublicListener:
|
||||
Description: The ARN of the public load balancer's Listener
|
||||
Value: !Ref PublicLoadBalancerListener
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicListener' ] ]
|
||||
VPCId:
|
||||
Description: The ID of the VPC that this stack is deployed in
|
||||
Value: !Ref 'VPC'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ]
|
||||
PublicSubnetOne:
|
||||
Description: Public subnet one
|
||||
Value: !Ref 'PublicSubnetOne'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ]
|
||||
PublicSubnetTwo:
|
||||
Description: Public subnet two
|
||||
Value: !Ref 'PublicSubnetTwo'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwo' ] ]
|
||||
ECSSecurityGroup:
|
||||
Description: A security group used to allow ECS containers to receive traffic
|
||||
Value: !Ref 'ECSSecurityGroup'
|
||||
Export:
|
||||
Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSSecurityGroup' ] ]
|
||||
@@ -0,0 +1 @@
|
||||
<mxfile modified="2020-05-11T21:18:00.281Z" host="app.diagrams.net" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36" etag="d22WT4Cb11cg7E6o-Mdr" version="13.0.9" type="device"><diagram id="Ht1M8jgEwFfnCIfOTk4-" name="Page-1">7VvZcuI4FP0aHpuyJK+PYUk6VemaTNHTyxNlsGI8MRYtiy1fP5KRwZbEkkBI00OKqljXspZ7z7mLDA3UHi/uaDgZfSERThvQihYN1GlACJDj8H9CslxJPM9eCWKaRLLTRtBLXrAUWlI6TSKc1zoyQlKWTOrCIckyPGQ1WUgpmde7PZG0PuskjLEm6A3DVJd+TyI2klLgBpsbn3ESj+TUPvRWN8Zh2VnuJB+FEZlXRKjbQG1KCFtdjRdtnArllXpZPXe75e56YRRn7JAHHuJp6+UXX9i3PB/cv7TDr5/+/SRHmYXpVG74cTpIk6FY73SQYSbXzpalQiYkyVihVKfFP3zOttVw+J22aDWhowjUtlcXAL0lxqgL1LZXFwB1eKDMD9QFVgRaqza8pcxvVRbIP6hFpixNMtxew8/iwpiGUcLN0iYpoVyWkYxrrzVi45S3AL+cjxKGe5NwKLQ659ThsieSMUkAAMu2VLx4hgNoIq7Hi1hwrRnOc7sZUzKdFFPecwoY7/ZzPJzShC37m849RskzlsvNi0a52AZE0PZ9YIslJGlakXeDW9R1uXyGKUs4SW7SJBaTMiKGDWUrxU9MDMu3l2TxQ9HqIEtuyTRPFOYjHMnlSEjyKfBiK9bBmkHc9WAyxowueRf5AHIk6aTXQaU7mVc47EvZqEJf4IICIYX7kK4jXg+/YRe/kAQzk22Eh3Dxcu9977wMnl37S/SrP/hUeoIK2749tq8Mu3yGzSbDg3kkl3oiCt3ctLyWb6aQxhcDq7ZSCLh1CgFLp5BrGyiEAut4/hiDla0HK5rMQoav0eoP4tL/L1o5wQdHq0U6ve3jv2af7eXPX2zwzwu+m5pSwyvbrmz7Hdh2VGBT2QbgmdlmjG2OxrYO36zVk0Sz7grzqIQz5BWawZwbt+27VUWCrVZSYaXYZD2UYs0T+EAXKlZxDFYptV9LN8AJ0g1juo6utfHV//15/k+tjc/u/4xkA57Gtm67JzwgprOEQ+G3dn3HGcQ/wPUhU6X1bq5Pj0YN6KZivwN+EYuLm8mE+8KQJZxZ5T1a3nwgYaRLue7CbIhpeYcvbT2cZl+j86hxobTjQzjA6SPJk2ItqDMgjJHxXiYOucX4Wmqux+BGUDPc7LSf8o31B+U+NCdw6/gOsrd7uROgBdp1tEDXagKkl+aBjhcPvhNc/P1wuRfKLmKmG46FGbJBPik0oqLkjpcY83B5YSBJ5P76sVy+GRvuu2Jj7SU22PhYYARbnPrXMH8+zJhqVETwVsyvKVEo2PO7VpV8nYTygVb2zggVGlCN0rGcNg89htjxVPydAU77Upswn6zU8ZQsxDrMuQ7FOZnSIV5lOi3eNOU8eJifBmk2UGKWryPNM4Qs770iFtSzdQ1gOIpxqVkBBxKTLEy7G2nVLDiLbsT7Q2HclAyfhSgdFO3SxgVsQsrKfpLI/MnbJF17Cy0psZ2WK2DKE84sWiOL24Iuf1QbP0WD59ey2VlUb3aW1dYjpgnXo4DW7jRxBZJdepSU5fuKMdvv84VOdwKmmsSUR8gUpzyczuqvWE2QkMM9irpqAz6lgIeBq+Wlq33K56ovRZWhgFJ2Il/PcVeq0MYqYLre6BHIda/IPQC5ewHpfBwgoXVCRCrVAArOj0hTPndJiHw7iIKPAxGyAhVEb0OQXX7npkSQbTWtyh84CEzcECIJX3eTZ1vblw81DuxcpdYfOAqWVys4LbL1hHRXpXKRBQh8femx/mrS5sSr/LITOlHVqhYmgZ4uBk3D8a5jH58wGl9vwf1I4NZiMcW9vx94x/uM+6dMHERdFCxolOt4gF0PFV8aeL9S1LXtmsWBZ6/DWNXoUDe5v8N1HmXySy8Rtpplb1ZvSOp3keJDwh9Qwh+0m77/tgjoAnNsOXXMc3bHsNf2PzrmGd8s6qi/nsH8LmcwR70yVc9gkGs3TXH1nY5hjGC7+Fp2kbAf5dz8ulLJ8tamkBWNZaVhLmMVP/o23+3ovns7zz/EVSPrrZWK6g8PLHTPXZvY1hn8tP4C9MKoc/pjoNfyZ1f8Oyz3OQt/7KBekEOVBE0L2bYPPcd3oeW9MQdybGVYHh38wHUc4PHQgdR3PqdKidz6bhTq7O2/L4VS64ojUyje3PwCZ9V98zsm1P0P</diagram></mxfile>
|
||||
164
aws/cloudformation/rds-in-private-subnet/service.yml
Normal file
164
aws/cloudformation/rds-in-private-subnet/service.yml
Normal file
@@ -0,0 +1,164 @@
|
||||
AWSTemplateFormatVersion: '2010-09-09'
|
||||
Description: Deploy a service on AWS Fargate, hosted in two public subnets and accessible via a public load balancer.
|
||||
Derived from a template at https://github.com/nathanpeck/aws-cloudformation-fargate.
|
||||
Parameters:
|
||||
NetworkStackName:
|
||||
Type: String
|
||||
Description: The name of the networking stack that
|
||||
these resources are put into.
|
||||
DatabaseStackName:
|
||||
Type: String
|
||||
Description: The name of the database stack with the database this service should connect to.
|
||||
ServiceName:
|
||||
Type: String
|
||||
Description: A human-readable name for the service.
|
||||
HealthCheckPath:
|
||||
Type: String
|
||||
Default: /health
|
||||
Description: Path to perform the healthcheck on each instance.
|
||||
HealthCheckIntervalSeconds:
|
||||
Type: Number
|
||||
Default: 5
|
||||
Description: Number of seconds to wait between each health check.
|
||||
ImageUrl:
|
||||
Type: String
|
||||
Description: The url of a docker image that will handle incoming traffic.
|
||||
ContainerPort:
|
||||
Type: Number
|
||||
Default: 80
|
||||
Description: The port number the application inside the docker container
|
||||
is binding to.
|
||||
ContainerCpu:
|
||||
Type: Number
|
||||
Default: 256
|
||||
Description: How much CPU to give the container. 1024 is 1 CPU.
|
||||
ContainerMemory:
|
||||
Type: Number
|
||||
Default: 512
|
||||
Description: How much memory in megabytes to give the container.
|
||||
Path:
|
||||
Type: String
|
||||
Default: "*"
|
||||
Description: A path on the public load balancer that this service
|
||||
should be connected to.
|
||||
DesiredCount:
|
||||
Type: Number
|
||||
Default: 2
|
||||
Description: How many copies of the service task to run.
|
||||
|
||||
Resources:
|
||||
|
||||
TargetGroup:
|
||||
Type: AWS::ElasticLoadBalancingV2::TargetGroup
|
||||
Properties:
|
||||
HealthCheckIntervalSeconds: !Ref 'HealthCheckIntervalSeconds'
|
||||
HealthCheckPath: !Ref 'HealthCheckPath'
|
||||
HealthCheckProtocol: HTTP
|
||||
HealthCheckTimeoutSeconds: 5
|
||||
HealthyThresholdCount: 2
|
||||
TargetType: ip
|
||||
Name: !Ref 'ServiceName'
|
||||
Port: !Ref 'ContainerPort'
|
||||
Protocol: HTTP
|
||||
UnhealthyThresholdCount: 2
|
||||
VpcId:
|
||||
Fn::ImportValue:
|
||||
!Join [':', [!Ref 'NetworkStackName', 'VPCId']]
|
||||
|
||||
LoadBalancerRule:
|
||||
Type: AWS::ElasticLoadBalancingV2::ListenerRule
|
||||
Properties:
|
||||
Actions:
|
||||
- TargetGroupArn: !Ref 'TargetGroup'
|
||||
Type: 'forward'
|
||||
Conditions:
|
||||
- Field: path-pattern
|
||||
Values: [!Ref 'Path']
|
||||
ListenerArn:
|
||||
Fn::ImportValue:
|
||||
!Join [':', [!Ref 'NetworkStackName', 'PublicListener']]
|
||||
Priority: 1
|
||||
|
||||
LogGroup:
|
||||
Type: AWS::Logs::LogGroup
|
||||
Properties:
|
||||
LogGroupName: !Ref 'ServiceName'
|
||||
RetentionInDays: 1
|
||||
|
||||
TaskDefinition:
|
||||
Type: AWS::ECS::TaskDefinition
|
||||
Properties:
|
||||
Family: !Ref 'ServiceName'
|
||||
Cpu: !Ref 'ContainerCpu'
|
||||
Memory: !Ref 'ContainerMemory'
|
||||
NetworkMode: awsvpc
|
||||
RequiresCompatibilities:
|
||||
- FARGATE
|
||||
ExecutionRoleArn:
|
||||
Fn::ImportValue:
|
||||
!Join [':', [!Ref 'NetworkStackName', 'ECSTaskExecutionRole']]
|
||||
ContainerDefinitions:
|
||||
- Name: !Ref 'ServiceName'
|
||||
Cpu: !Ref 'ContainerCpu'
|
||||
Memory: !Ref 'ContainerMemory'
|
||||
Image: !Ref 'ImageUrl'
|
||||
Environment:
|
||||
- Name: SPRING_DATASOURCE_URL
|
||||
Value: !Join
|
||||
- ''
|
||||
- - 'jdbc:postgresql://'
|
||||
- Fn::ImportValue: !Join [':', [!Ref 'DatabaseStackName', 'EndpointAddress']]
|
||||
- ':'
|
||||
- Fn::ImportValue: !Join [':', [!Ref 'DatabaseStackName', 'EndpointPort']]
|
||||
- '/'
|
||||
- Fn::ImportValue: !Join [':', [!Ref 'DatabaseStackName', 'DBName']]
|
||||
- Name: SPRING_DATASOURCE_USERNAME
|
||||
Value: !Join
|
||||
- ''
|
||||
- - '{{resolve:secretsmanager:'
|
||||
- Fn::ImportValue: !Join [':', [!Ref 'DatabaseStackName', 'Secret']]
|
||||
- ':SecretString:username}}'
|
||||
- Name: SPRING_DATASOURCE_PASSWORD
|
||||
Value: !Join
|
||||
- ''
|
||||
- - '{{resolve:secretsmanager:'
|
||||
- Fn::ImportValue: !Join [':', [!Ref 'DatabaseStackName', 'Secret']]
|
||||
- ':SecretString:password}}'
|
||||
PortMappings:
|
||||
- ContainerPort: !Ref 'ContainerPort'
|
||||
LogConfiguration:
|
||||
LogDriver: 'awslogs'
|
||||
Options:
|
||||
awslogs-group: !Ref 'ServiceName'
|
||||
awslogs-region: !Ref AWS::Region
|
||||
awslogs-stream-prefix: !Ref 'ServiceName'
|
||||
|
||||
Service:
|
||||
Type: AWS::ECS::Service
|
||||
DependsOn: LoadBalancerRule
|
||||
Properties:
|
||||
ServiceName: !Ref 'ServiceName'
|
||||
Cluster:
|
||||
Fn::ImportValue:
|
||||
!Join [':', [!Ref 'NetworkStackName', 'ClusterName']]
|
||||
LaunchType: FARGATE
|
||||
DeploymentConfiguration:
|
||||
MaximumPercent: 200
|
||||
MinimumHealthyPercent: 50
|
||||
DesiredCount: !Ref 'DesiredCount'
|
||||
NetworkConfiguration:
|
||||
AwsvpcConfiguration:
|
||||
AssignPublicIp: ENABLED
|
||||
SecurityGroups:
|
||||
- Fn::ImportValue:
|
||||
!Join [':', [!Ref 'NetworkStackName', 'ECSSecurityGroup']]
|
||||
Subnets:
|
||||
- Fn::ImportValue:
|
||||
!Join [':', [!Ref 'NetworkStackName', 'PublicSubnetOne']]
|
||||
- Fn::ImportValue:
|
||||
!Join [':', [!Ref 'NetworkStackName', 'PublicSubnetTwo']]
|
||||
TaskDefinition: !Ref 'TaskDefinition'
|
||||
LoadBalancers:
|
||||
- ContainerName: !Ref 'ServiceName'
|
||||
ContainerPort: !Ref 'ContainerPort'
|
||||
TargetGroupArn: !Ref 'TargetGroup'
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '2.1.3.RELEASE'
|
||||
id 'org.springframework.boot' version '2.3.0.RELEASE'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
group = 'io.reflectoring'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = '11'
|
||||
sourceCompatibility = '13'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@@ -23,6 +23,7 @@ dependencies {
|
||||
testImplementation('org.junit.jupiter:junit-jupiter:5.4.0')
|
||||
testImplementation('org.springframework.boot:spring-boot-starter-test'){
|
||||
exclude group: 'junit', module: 'junit'
|
||||
exclude group: 'org.junit.vintage'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
0
spring-boot/configuration/gradlew
vendored
Normal file → Executable file
0
spring-boot/configuration/gradlew
vendored
Normal file → Executable file
@@ -0,0 +1,14 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.reflectoring.validation;
|
||||
|
||||
import io.reflectoring.validation.thirdparty.ThirdPartyComponentProperties;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(AppProperties.class)
|
||||
class AppConfiguration {
|
||||
|
||||
@Bean
|
||||
public static ReportEmailAddressValidator configurationPropertiesValidator() {
|
||||
return new ReportEmailAddressValidator();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Validated
|
||||
@ConfigurationProperties(prefix = "app.third-party.properties")
|
||||
public ThirdPartyComponentProperties thirdPartyComponentProperties() {
|
||||
return new ThirdPartyComponentProperties();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.reflectoring.validation;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Validated
|
||||
@ConfigurationProperties(prefix = "app.properties")
|
||||
class AppProperties implements Validator {
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@Valid
|
||||
private ReportProperties report;
|
||||
|
||||
private static final String APP_BASE_NAME = "Application";
|
||||
|
||||
public boolean supports(Class clazz) {
|
||||
return AppProperties.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
public void validate(Object target, Errors errors) {
|
||||
|
||||
AppProperties appProperties = (AppProperties) target;
|
||||
if (!appProperties.getName().endsWith(APP_BASE_NAME)) {
|
||||
errors.rejectValue("name", "field.name.malformed",
|
||||
new Object[]{APP_BASE_NAME},
|
||||
"The application name must contain [" + APP_BASE_NAME + "] base name");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public ReportProperties getReport() {
|
||||
return report;
|
||||
}
|
||||
|
||||
public void setReport(ReportProperties report) {
|
||||
this.report = report;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.reflectoring.validation;
|
||||
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
class ReportEmailAddressValidator implements Validator {
|
||||
|
||||
private static final String EMAIL_DOMAIN = "@analysisapp.com";
|
||||
|
||||
public boolean supports(Class clazz) {
|
||||
return ReportProperties.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
public void validate(Object target, Errors errors) {
|
||||
|
||||
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "emailAddress", "field.required");
|
||||
|
||||
ReportProperties reportProperties = (ReportProperties) target;
|
||||
if (!reportProperties.getEmailAddress().endsWith(EMAIL_DOMAIN)) {
|
||||
errors.rejectValue("emailAddress", "field.domain.required",
|
||||
new Object[]{EMAIL_DOMAIN},
|
||||
"The email address must contain [" + EMAIL_DOMAIN + "] domain");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.reflectoring.validation;
|
||||
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
|
||||
class ReportProperties {
|
||||
|
||||
private Boolean sendEmails = Boolean.FALSE;
|
||||
|
||||
private ReportType type = ReportType.HTML;
|
||||
|
||||
@Min(value = 7)
|
||||
@Max(value = 30)
|
||||
private Integer intervalInDays;
|
||||
|
||||
@Email
|
||||
private String emailAddress;
|
||||
|
||||
public Boolean getSendEmails() {
|
||||
return sendEmails;
|
||||
}
|
||||
|
||||
public void setSendEmails(Boolean sendEmails) {
|
||||
this.sendEmails = sendEmails;
|
||||
}
|
||||
|
||||
public ReportType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(ReportType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Integer getIntervalInDays() {
|
||||
return intervalInDays;
|
||||
}
|
||||
|
||||
public void setIntervalInDays(Integer intervalInDays) {
|
||||
this.intervalInDays = intervalInDays;
|
||||
}
|
||||
|
||||
public String getEmailAddress() {
|
||||
return emailAddress;
|
||||
}
|
||||
|
||||
public void setEmailAddress(String emailAddress) {
|
||||
this.emailAddress = emailAddress;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package io.reflectoring.validation;
|
||||
|
||||
enum ReportType {
|
||||
HTML, PLAIN_TEXT
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.reflectoring.validation.thirdparty;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* We assume that this bean comes from another jar file
|
||||
*/
|
||||
public class ThirdPartyComponentProperties {
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
app.properties.name=Analysis Application
|
||||
app.properties.report.send-emails=true
|
||||
app.properties.report.type=PLAIN_TEXT
|
||||
app.properties.report.interval-in-days=14
|
||||
app.properties.report.email-address=manager@analysisapp.com
|
||||
# third-party component properties
|
||||
app.third-party.properties.name=Third Party!
|
||||
@@ -1,5 +1,4 @@
|
||||
myapp.mail.enabled=true
|
||||
myapp
|
||||
myapp.mail.pauseBetweenMails=5s
|
||||
myapp.mail.maxAttachmentSize=1MB
|
||||
myapp.mail.smtpServers[0]=server1
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
package io.reflectoring.configuration.mail;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(properties = {
|
||||
"myapp.mail.enabled=asd",
|
||||
"myapp.mail.defaultSubject=hello",
|
||||
"myapp.mail.pauseBetweenMails=5s",
|
||||
"myapp.mail.maxAttachmentSize=1MB",
|
||||
"myapp.mail.smtpServers[0]=server1",
|
||||
"myapp.mail.smtpServers[1]=server2",
|
||||
"myapp.mail.maxAttachmentWeight=5kg"
|
||||
"myapp.mail.enabled=asd",
|
||||
"myapp.mail.defaultSubject=hello",
|
||||
"myapp.mail.pauseBetweenMails=5s",
|
||||
"myapp.mail.maxAttachmentSize=1MB",
|
||||
"myapp.mail.smtpServers[0]=server1",
|
||||
"myapp.mail.smtpServers[1]=server2",
|
||||
"myapp.mail.maxAttachmentWeight=5kg"
|
||||
})
|
||||
class MailModuleTestWithAllProperties {
|
||||
|
||||
@Autowired(required = false)
|
||||
private MailModuleProperties mailModuleProperties;
|
||||
@Autowired(required = false)
|
||||
private MailModuleProperties mailModuleProperties;
|
||||
|
||||
@Test
|
||||
void propertiesAreLoaded() {
|
||||
assertThat(mailModuleProperties).isNotNull();
|
||||
assertThat(mailModuleProperties.getDefaultSubject()).isEqualTo("hello");
|
||||
assertThat(mailModuleProperties.getEnabled()).isTrue();
|
||||
assertThat(mailModuleProperties.getPauseBetweenMails()).isEqualByComparingTo(Duration.ofSeconds(5));
|
||||
assertThat(mailModuleProperties.getMaxAttachmentSize()).isEqualByComparingTo(DataSize.ofMegabytes(1));
|
||||
assertThat(mailModuleProperties.getSmtpServers()).hasSize(2);
|
||||
assertThat(mailModuleProperties.getMaxAttachmentWeight().getGrams()).isEqualTo(5000L);
|
||||
}
|
||||
@Test
|
||||
void propertiesAreLoaded() {
|
||||
assertThat(mailModuleProperties).isNotNull();
|
||||
assertThat(mailModuleProperties.getDefaultSubject()).isEqualTo("hello");
|
||||
assertThat(mailModuleProperties.getEnabled()).isTrue();
|
||||
assertThat(mailModuleProperties.getPauseBetweenMails()).isEqualByComparingTo(Duration.ofSeconds(5));
|
||||
assertThat(mailModuleProperties.getMaxAttachmentSize()).isEqualByComparingTo(DataSize.ofMegabytes(1));
|
||||
assertThat(mailModuleProperties.getSmtpServers()).hasSize(2);
|
||||
assertThat(mailModuleProperties.getMaxAttachmentWeight().getGrams()).isEqualTo(5000L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package io.reflectoring.validation;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBindException;
|
||||
import org.springframework.boot.context.properties.bind.validation.BindValidationException;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertiesPropertySource;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* We create Spring Application dynamically to catch and test application context startup exceptions
|
||||
*/
|
||||
class PropertiesInvalidInputTest {
|
||||
|
||||
SpringApplication application;
|
||||
Properties properties;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
// create Spring Application dynamically
|
||||
application = new SpringApplication(ValidationApplication.class);
|
||||
|
||||
// setting test properties for our Spring Application
|
||||
properties = new Properties();
|
||||
|
||||
ConfigurableEnvironment environment = new StandardEnvironment();
|
||||
MutablePropertySources propertySources = environment.getPropertySources();
|
||||
propertySources.addFirst(new PropertiesPropertySource("application-test", properties));
|
||||
application.setEnvironment(environment);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGivenNameEmpty_thenNotBlankValidationFails() {
|
||||
|
||||
properties.put("app.properties.name", "");
|
||||
|
||||
assertThatThrownBy(application::run)
|
||||
.isInstanceOf(ConfigurationPropertiesBindException.class)
|
||||
.hasRootCauseInstanceOf(BindValidationException.class)
|
||||
.hasStackTraceContaining("Field error in object 'app.properties' on field 'name'")
|
||||
.hasStackTraceContaining("[must not be blank]");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGivenNameDoesNotContainBaseName_thenCustomAppPropertiesValidatorFails() {
|
||||
|
||||
properties.put("app.properties.name", "My App");
|
||||
|
||||
assertThatThrownBy(application::run)
|
||||
.isInstanceOf(ConfigurationPropertiesBindException.class)
|
||||
.hasRootCauseInstanceOf(BindValidationException.class)
|
||||
.hasStackTraceContaining("Field error in object 'app.properties' on field 'name'")
|
||||
.hasStackTraceContaining("[The application name must contain [Application] base name]");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGivenReportIntervalInDaysMoreThan30_thenMaxValidationFails() {
|
||||
|
||||
properties.put("app.properties.report.interval-in-days", "31");
|
||||
|
||||
assertThatThrownBy(application::run)
|
||||
.isInstanceOf(ConfigurationPropertiesBindException.class)
|
||||
.hasRootCauseInstanceOf(BindValidationException.class)
|
||||
.hasStackTraceContaining("rejected value [31]");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGivenReportIntervalInDaysLessThan7_thenMinValidationFails() {
|
||||
|
||||
properties.put("app.properties.report.interval-in-days", "6");
|
||||
|
||||
assertThatThrownBy(application::run)
|
||||
.isInstanceOf(ConfigurationPropertiesBindException.class)
|
||||
.hasRootCauseInstanceOf(BindValidationException.class)
|
||||
.hasStackTraceContaining("rejected value [6]");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGivenReportEmailAddressIsNotWellFormed_thenEmailValidationFails() {
|
||||
|
||||
properties.put("app.properties.report.email-address", "manager.analysisapp.com");
|
||||
|
||||
assertThatThrownBy(application::run)
|
||||
.isInstanceOf(ConfigurationPropertiesBindException.class)
|
||||
.hasRootCauseInstanceOf(BindValidationException.class)
|
||||
.hasStackTraceContaining("rejected value [manager.analysisapp.com]");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGivenReportEmailAddressDoesNotContainAnalysisappDomain_thenCustomEmailValidatorFails() {
|
||||
|
||||
properties.put("app.properties.report.email-address", "manager@notanalysisapp.com");
|
||||
|
||||
assertThatThrownBy(application::run)
|
||||
.isInstanceOf(ConfigurationPropertiesBindException.class)
|
||||
.hasRootCauseInstanceOf(BindValidationException.class)
|
||||
.hasStackTraceContaining("Field error in object 'app.properties.report' on field 'emailAddress'")
|
||||
.hasStackTraceContaining("[The email address must contain [@analysisapp.com] domain]");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenGivenThirdPartyComponentNameIsEmpty_thenNotBlankValidationFails() {
|
||||
|
||||
properties.put("app.third-party.properties.name", "");
|
||||
|
||||
assertThatThrownBy(application::run)
|
||||
.isInstanceOf(ConfigurationPropertiesBindException.class)
|
||||
.hasRootCauseInstanceOf(BindValidationException.class)
|
||||
.hasStackTraceContaining("Field error in object 'app.third-party.properties' on field 'name'")
|
||||
.hasStackTraceContaining("[must not be blank]");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.reflectoring.validation;
|
||||
|
||||
import io.reflectoring.validation.thirdparty.ThirdPartyComponentProperties;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(properties = {
|
||||
"app.properties.name=My Test Application",
|
||||
"app.properties.report.send-emails=true",
|
||||
"app.properties.report.type=PLAIN_TEXT",
|
||||
"app.properties.report.interval-in-days=14",
|
||||
"app.properties.report.email-address=manager@analysisapp.com",
|
||||
"app.third-party.properties.name=Third Party!"
|
||||
}, classes = {AppConfiguration.class})
|
||||
class PropertiesValidInputTest {
|
||||
|
||||
@Autowired
|
||||
AppProperties appProperties;
|
||||
|
||||
@Autowired
|
||||
ThirdPartyComponentProperties thirdPartyComponentProperties;
|
||||
|
||||
@Test
|
||||
void appPropertiesAreLoaded() {
|
||||
assertThat(appProperties).isNotNull();
|
||||
assertThat(appProperties.getName()).isEqualTo("My Test Application");
|
||||
assertThat(appProperties.getReport()).isNotNull();
|
||||
assertThat(appProperties.getReport().getSendEmails()).isTrue();
|
||||
assertThat(appProperties.getReport().getType()).isEqualTo(ReportType.PLAIN_TEXT);
|
||||
assertThat(appProperties.getReport().getIntervalInDays()).isEqualTo(14);
|
||||
assertThat(appProperties.getReport().getEmailAddress()).isEqualTo("manager@analysisapp.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void thirdPartyComponentPropertiesAreLoaded() {
|
||||
assertThat(thirdPartyComponentProperties).isNotNull();
|
||||
assertThat(thirdPartyComponentProperties.getName()).isEqualTo("Third Party!");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.reflectoring.validation;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
@SpringBootApplication
|
||||
@PropertySource("classpath:application-validation.properties")
|
||||
public class ValidationApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ValidationApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.reflectoring.testcontainers;
|
||||
package de.kuksin.testcontainers;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.reflectoring.testcontainers.entities;
|
||||
package de.kuksin.testcontainers.entities;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.reflectoring.testcontainers.repositories;
|
||||
package de.kuksin.testcontainers.repositories;
|
||||
|
||||
import io.reflectoring.testcontainers.entities.Car;
|
||||
import de.kuksin.testcontainers.entities.Car;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.UUID;
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.reflectoring.testcontainers;
|
||||
package de.kuksin.testcontainers;
|
||||
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
@@ -19,10 +19,12 @@ public class AbstractIntegrationTest {
|
||||
|
||||
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>();
|
||||
|
||||
public static Map<String, String> getProperties() {
|
||||
private static void startContainers() {
|
||||
Startables.deepStart(Stream.of(postgres)).join();
|
||||
// we can add further containers here like rabbitmq or other database
|
||||
}
|
||||
|
||||
private static Map<String, String> createConnectionConfiguration() {
|
||||
return Map.of(
|
||||
"spring.datasource.url", postgres.getJdbcUrl(),
|
||||
"spring.datasource.username", postgres.getUsername(),
|
||||
@@ -33,10 +35,11 @@ public class AbstractIntegrationTest {
|
||||
|
||||
@Override
|
||||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
startContainers();
|
||||
ConfigurableEnvironment environment = applicationContext.getEnvironment();
|
||||
MapPropertySource testcontainers = new MapPropertySource(
|
||||
"testcontainers",
|
||||
(Map) getProperties()
|
||||
(Map) createConnectionConfiguration()
|
||||
);
|
||||
environment.getPropertySources().addFirst(testcontainers);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.reflectoring.testcontainers;
|
||||
package de.kuksin.testcontainers;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
Reference in New Issue
Block a user