Compare commits
241 Commits
logging-fo
...
18-validat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97bf254d85 | ||
|
|
381fe85e39 | ||
|
|
a418f21ff1 | ||
|
|
735d80d72f | ||
|
|
3eb97d81ec | ||
|
|
27e1428488 | ||
|
|
7b63198a08 | ||
|
|
45c947ddc1 | ||
|
|
e41458b7cd | ||
|
|
03ab12c64b | ||
|
|
f183c92661 | ||
|
|
109dacd121 | ||
|
|
0bff861d25 | ||
|
|
51be8954a9 | ||
|
|
4bd0201900 | ||
|
|
074b118bff | ||
|
|
1a4c9421dc | ||
|
|
6168a9a47a | ||
|
|
b6cd9b8138 | ||
|
|
cc153bfd06 | ||
|
|
cf3a36e108 | ||
|
|
2e6e26193f | ||
|
|
ae8ec346fd | ||
|
|
caf408a550 | ||
|
|
4543443a82 | ||
|
|
ae813adb18 | ||
|
|
6a27f35b2a | ||
|
|
1f9fbc2543 | ||
|
|
52f8dfc91c | ||
|
|
a5305e266f | ||
|
|
507ff901b4 | ||
|
|
8b5e2ce3a4 | ||
|
|
ebd2362c1c | ||
|
|
72f40744dd | ||
|
|
43f9e32282 | ||
|
|
f85a0f36ae | ||
|
|
1f2b333a8a | ||
|
|
4f056283b3 | ||
|
|
054d177ef2 | ||
|
|
84f441dbeb | ||
|
|
b3be06ea00 | ||
|
|
1fa7f545fd | ||
|
|
32f3e4cefa | ||
|
|
5109d98167 | ||
|
|
dc12f67ae4 | ||
|
|
8c34f22b7e | ||
|
|
ffa9ac6d5e | ||
|
|
0b9e63571e | ||
|
|
3b69be714e | ||
|
|
2ae9c0b2ae | ||
|
|
ef9fe2c506 | ||
|
|
9653304df2 | ||
|
|
b7dc19cb8d | ||
|
|
16bdac5240 | ||
|
|
53a7cd866f | ||
|
|
78fd7fb89f | ||
|
|
94ebb0bde7 | ||
|
|
681d34b775 | ||
|
|
d8ca7d3f04 | ||
|
|
bf68901255 | ||
|
|
ab949c2a0b | ||
|
|
90f7b1ad39 | ||
|
|
4b959cb797 | ||
|
|
670c4c845a | ||
|
|
531d05de64 | ||
|
|
3a929134ba | ||
|
|
f1aa143b96 | ||
|
|
f05e8f75cd | ||
|
|
190f2511af | ||
|
|
4ecc79977a | ||
|
|
7e4b6d4cac | ||
|
|
e023c7c42f | ||
|
|
a857ad5455 | ||
|
|
51460691bb | ||
|
|
01b0ef3eb8 | ||
|
|
4f006376c1 | ||
|
|
01db1e9805 | ||
|
|
a989129816 | ||
|
|
4ceee9a453 | ||
|
|
c90578311b | ||
|
|
7cd125ee33 | ||
|
|
76e97842a2 | ||
|
|
92aef0228e | ||
|
|
ca32144e80 | ||
|
|
e8ef69917a | ||
|
|
5da17c5a20 | ||
|
|
8a36085425 | ||
|
|
1916f5b2af | ||
|
|
c69957d1b8 | ||
|
|
426d1e77c6 | ||
|
|
ec681b5abc | ||
|
|
57ff03707c | ||
|
|
761fdd4d7a | ||
|
|
939e864a4a | ||
|
|
84c593016b | ||
|
|
e0a456724d | ||
|
|
c8a15d4d4d | ||
|
|
34e0bfc156 | ||
|
|
dfbc0446c9 | ||
|
|
0b9a101b08 | ||
|
|
1b9f2426a4 | ||
|
|
3ab8517320 | ||
|
|
77151d1752 | ||
|
|
b73731159f | ||
|
|
bfd004b73a | ||
|
|
71d2a32f2f | ||
|
|
1e4e88c05e | ||
|
|
1f4484f25f | ||
|
|
ab5143cb2f | ||
|
|
5fab70b931 | ||
|
|
3be25df928 | ||
|
|
74f0e28e8e | ||
|
|
f66aa05afb | ||
|
|
66d275d667 | ||
|
|
34211bfac9 | ||
|
|
1380621113 | ||
|
|
47baf5ea91 | ||
|
|
61ed2eaaa8 | ||
|
|
d55ee992f3 | ||
|
|
9a378b5763 | ||
|
|
c4ba025b17 | ||
|
|
4ca9b30b94 | ||
|
|
6e0af82475 | ||
|
|
7494ff7f89 | ||
|
|
327e849575 | ||
|
|
a0ad81a332 | ||
|
|
0847972b5a | ||
|
|
1e9c30733d | ||
|
|
90657e3a6b | ||
|
|
26739aada2 | ||
|
|
dabc601384 | ||
|
|
0be1335d24 | ||
|
|
1437ac4429 | ||
|
|
5b76cbac62 | ||
|
|
8ca0e6e380 | ||
|
|
b6d167f4ae | ||
|
|
7934eed0ab | ||
|
|
ec2bdc9327 | ||
|
|
5fe5deca66 | ||
|
|
3fb809911a | ||
|
|
1809361301 | ||
|
|
01828e504d | ||
|
|
3461549263 | ||
|
|
a2bb00faf1 | ||
|
|
9e9b030c3f | ||
|
|
71af6ee95d | ||
|
|
b8e0845744 | ||
|
|
8baf275c8f | ||
|
|
92c266586c | ||
|
|
802046466f | ||
|
|
895d48c163 | ||
|
|
6667bcde4d | ||
|
|
5975aff396 | ||
|
|
44fb22ff50 | ||
|
|
dff7cf37f0 | ||
|
|
659a602e97 | ||
|
|
d34df7ad4e | ||
|
|
4c1318cc50 | ||
|
|
aa09ca31cb | ||
|
|
7712f41e3e | ||
|
|
2690f6b14d | ||
|
|
fa74e78e5d | ||
|
|
2c50fa9a12 | ||
|
|
c48921f554 | ||
|
|
df9575152f | ||
|
|
203c8d053a | ||
|
|
9cf783c0a4 | ||
|
|
9049c0f970 | ||
|
|
58c58e02d4 | ||
|
|
9f84fcd49f | ||
|
|
5646e0f169 | ||
|
|
43e82f71d2 | ||
|
|
8877f30ebc | ||
|
|
b0ae545cd8 | ||
|
|
fb0aeb0a36 | ||
|
|
b3532e2f1e | ||
|
|
2c8d828ca1 | ||
|
|
d660463d74 | ||
|
|
3207a3bc82 | ||
|
|
14039b84d5 | ||
|
|
7528cd0069 | ||
|
|
f7e158a83c | ||
|
|
d01cccb574 | ||
|
|
71fad7e002 | ||
|
|
93f8074914 | ||
|
|
5ecb279f64 | ||
|
|
d5713b6ca3 | ||
|
|
188b53f526 | ||
|
|
86e8458980 | ||
|
|
8d8937c6bf | ||
|
|
5352631522 | ||
|
|
5f85a1cf67 | ||
|
|
576a726cbd | ||
|
|
027abc1271 | ||
|
|
037dca0127 | ||
|
|
b245679a55 | ||
|
|
375b46309b | ||
|
|
38d9b4ee72 | ||
|
|
93edb8c6f8 | ||
|
|
a4c3f21391 | ||
|
|
382c307ae9 | ||
|
|
3e2c23cf45 | ||
|
|
01242c5674 | ||
|
|
a58b5e670d | ||
|
|
a5a1381e1b | ||
|
|
da30072399 | ||
|
|
65c49a3ea7 | ||
|
|
5fb2d69ca7 | ||
|
|
a2df3dde0b | ||
|
|
ff2030eafd | ||
|
|
4e45af2fbd | ||
|
|
8b384c602b | ||
|
|
c11403d8fa | ||
|
|
e8786b1bc3 | ||
|
|
dab12b2c3c | ||
|
|
31515b8a35 | ||
|
|
aa05723b18 | ||
|
|
cac0c62b71 | ||
|
|
24d00fae37 | ||
|
|
03f1680103 | ||
|
|
884546b69c | ||
|
|
be8be2efc4 | ||
|
|
0e2f10862e | ||
|
|
441cd4f801 | ||
|
|
de7fa53d55 | ||
|
|
b4bec09a3e | ||
|
|
818e162c7f | ||
|
|
a18ad84da2 | ||
|
|
d60f95b6c0 | ||
|
|
52bc347d12 | ||
|
|
5e93dcb84b | ||
|
|
3b0eb77b5d | ||
|
|
a9c9faba1d | ||
|
|
8672d163e2 | ||
|
|
3eefd80192 | ||
|
|
5a36a2365e | ||
|
|
5f675f91a8 | ||
|
|
3559378d1a | ||
|
|
b72ab0d580 | ||
|
|
ed45066496 | ||
|
|
ddb3e08ee5 |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
**/.idea/
|
||||
**/*.iml
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Gradle: org.projectlombok:lombok:1.16.20">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.16.20/ac76d9b956045631d1561a09289cbf472e077c01/lombok-1.16.20.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.16.20/69ebf81bb97bdb3c9581c171762bb4929cb5289c/lombok-1.16.20-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
13
.idea/modules/spring-boot-testing.iml
generated
13
.idea/modules/spring-boot-testing.iml
generated
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id=":spring-boot:spring-boot-testing" external.linked.project.path="$MODULE_DIR$/../../spring-boot/spring-boot-testing" external.root.project.path="$MODULE_DIR$/../.." external.system.id="GRADLE" external.system.module.group="reflectoring.io" external.system.module.version="0.0.1-SNAPSHOT" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing">
|
||||
<excludeFolder url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing/.gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/../../spring-boot/spring-boot-testing/out" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,5 +1,5 @@
|
||||
before_install:
|
||||
- chmod +x gradlew
|
||||
- chmod +x build-all.sh
|
||||
- |
|
||||
if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(.md)|^(LICENSE)'
|
||||
then
|
||||
@@ -7,7 +7,12 @@ before_install:
|
||||
exit
|
||||
fi
|
||||
|
||||
install: skip
|
||||
|
||||
script:
|
||||
- ./build-all.sh
|
||||
|
||||
language: java
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk11
|
||||
17
README.md
17
README.md
@@ -2,7 +2,20 @@
|
||||
|
||||
[](https://travis-ci.org/thombergs/code-examples)
|
||||
|
||||
This repo contains example projects which show how to use different java technologies.
|
||||
This repo contains example projects which show how to use different (not only) Java technologies.
|
||||
The examples are usually accompanied by a blog post on [https://reflectoring.io](https://reflectoring.io).
|
||||
|
||||
See the READMEs in each subdirectory of this repo for more information.
|
||||
See the READMEs in each subdirectory of this repo for more information on each module.
|
||||
|
||||
## Java Modules
|
||||
All Java modules require **Java 11** to compile and run.
|
||||
|
||||
### Building with Gradle
|
||||
|
||||
Each module should be an independent build and can be built by calling `./gradlew clean build` in the module directory.
|
||||
|
||||
All modules are listed in [build-all.sh](build-all.sh) to run in the CI pipeline.
|
||||
|
||||
### Non-Java Modules
|
||||
|
||||
Some folders contain non-Java projects. For those, refer to the README within the module folder.
|
||||
|
||||
32
aws/aws-hello-world/.gitignore
vendored
Normal file
32
aws/aws-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-hello-world/Dockerfile
Normal file
5
aws/aws-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
|
||||
24
aws/aws-hello-world/build.gradle
Normal file
24
aws/aws-hello-world/build.gradle
Normal file
@@ -0,0 +1,24 @@
|
||||
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'
|
||||
testImplementation('org.springframework.boot:spring-boot-starter-test') {
|
||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
BIN
aws/aws-hello-world/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
aws/aws-hello-world/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
@@ -1,6 +1,5 @@
|
||||
#Sun Jul 30 16:58:54 CEST 2017
|
||||
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
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-all.zip
|
||||
172
aws/aws-hello-world/gradlew
vendored
Executable file
172
aws/aws-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" "$@"
|
||||
0
gradlew.bat → aws/aws-hello-world/gradlew.bat
vendored
Normal file → Executable file
0
gradlew.bat → aws/aws-hello-world/gradlew.bat
vendored
Normal file → Executable file
1
aws/aws-hello-world/settings.gradle
Normal file
1
aws/aws-hello-world/settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'aws-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,14 @@
|
||||
package io.reflectoring.awshelloworld;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class HelloWorldController {
|
||||
|
||||
@GetMapping("/hello")
|
||||
public String helloWorld(){
|
||||
return "Hello AWS!";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.reflectoring.awshelloworld;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class AwsHelloWorldApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
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" "$@"
|
||||
0
spring-boot/modular/application/gradlew.bat → aws/aws-rds-hello-world/gradlew.bat
vendored
Normal file → Executable file
0
spring-boot/modular/application/gradlew.bat → aws/aws-rds-hello-world/gradlew.bat
vendored
Normal file → Executable file
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/)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<mxfile modified="2020-04-30T21:28:18.347Z" 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="X9Eef2gSXNvxkCnmY-_0" version="13.0.4" type="device"><diagram id="Ht1M8jgEwFfnCIfOTk4-" name="Page-1">7Vpbb+o4EP41PC7K/fLIrWcr9UiVWO05+4RM4garIc4aU6C/fseJc7ND6ekB9nQXVKmZsTMez3yf7TEM7Ml6/4WhfPWVxjgdWEa8H9jTgWWZphXCP6E5lBrPcEpFwkgsOzWKOXnFUmlI7ZbEeNPpyClNOcm7yohmGY54R4cYo7tutyeadkfNUYI1xTxCqa79RmK+qublhU3D75gkKzl0YPllwxpVneVMNisU011LZc8G9oRRysun9X6CUxG8Ki7le3dHWmvHGM74e15Y4cjav97736avy2fP+Rr/vVj+Vjn3gtKtnPGfjxPpMD9UUcgpyXgRSXcMf/DSxBi40DIR0tByFYUq+12FqUvCRlehyn5XYarmTWV8U3WwpdCkjnlDGd9oOQh/9phueUoyPKkxZ4AyYSgmkIsJTSkDXUYziN54xdcpSCY87laE43mOIhHVHfAFdE804xL1plXJMvDiHUBNLp7X+0QQbIh2G2eYMLrNiyHvAfe9rYuXPBKvc0afceXSwLItJwhMRwxE0lRx9QUzTgD6o5QkwiqnYhAkpRQ/cWER/CdZ8lBIU9uQPreGGI3G/jgAfYw2KxzL8EicwRB4fxTBZs0LWFAwXWPODtClesGTaJVrSQXeXUNMO5S6VYuTjiGVSC4GSW264Qs8SMr8AH3MUKPP43aZkkjQZ7vMML9R6fNTaYOjLSP8sGg6zwteSXffRzLQz8I7e+adj2n1OOdmmm0pTLPCoR1obKsZ2Gab6ZkFSC5COPvGtxvf/vt8c4Buftj6uL8A90xfI99sMgfFHLMXAshQqddzxNBS5468SeC1Q2oezZcKMCU7talzJMTvJsTvXf9MuycHVnip04arxX9geamA7hIeEvEwynNYDREnwK2qjVWNDxTFuhZogLIIs6oFXKvNaSntXT46bKhS94CWOH2kG1L4Yk+XlHO6PsnFCBIEvnQWn56FxB6iZqaLFCa2WFbz0JaBOzdwbef4OncGvMAZs4MX0w+GgQ6Yqmps48W3LgSX4DRc7kWwi13TQ2uRhmy5yYuIqCj5gjjeocMnAwmR81sk0v1+bHgXxUa9SrSw4esL+lWxodct5VL+B9o8vy+f6tZoW3difC2OIsZ+MDPa/JsSBobKlGeUiQioeZka7gQ2nJ4d46n4XAFRp843aJOX4Xgie+FH/4GH4Q3dsgiXx50xiH0HHxxtzgO2utatwGb07lx+z8blX6xKdm5o+3+gzXL/fbRZeo2oYQzHCa6CKxBBE5qhdNZo25nBWTwSN8kivymNnoUqXRZyleYCOYjxqp/cPODNO5LWO5R29nXcsSeQCmVOFtfggtizw/e28JcQoKqT4nTfbpwe2tIjZgTiKNDVKk7EfN9OMYSnAM5bgZWbBkw0wfzUwUPHTPvgXOGG4RSOcC9d5/ogIc09imq+wV+owM9Uap9yTvKl9t24YsdU6i9bvTEs56wZKgBaT/EnMOvdMPsRzJ6Eons1KFrqUvhhLAYKFs0rY1Ev8z8ZFveEf6/GhucWEkFqgCiEQ0u44NLpvhOu5UnpKng1TfXKyfgYXq1Tho7gFXIt6sm6m7yoPeqw7fYT7JhfWv/QUehSenBe8vQV3TfyXIU84dW4Y1ff+9VfjzjDIDDqj2LwvUxyHLdr1hOn6aNmfxFemc41eKVfWLx1mfUp76isH7+dqn9T0nwtUv1KxT7TxaaS7MDvu7vyw6FUtks81zlOrCMlHojNL2JK8DS/K7Jn/wA=</diagram></mxfile>
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 25 KiB |
247
aws/cloudformation/ecs-in-two-public-subnets/network.yml
Normal file
247
aws/cloudformation/ecs-in-two-public-subnets/network.yml
Normal file
@@ -0,0 +1,247 @@
|
||||
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: '*'
|
||||
|
||||
Outputs:
|
||||
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' ] ]
|
||||
139
aws/cloudformation/ecs-in-two-public-subnets/service.yml
Normal file
139
aws/cloudformation/ecs-in-two-public-subnets/service.yml
Normal file
@@ -0,0 +1,139 @@
|
||||
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:
|
||||
StackName:
|
||||
Type: String
|
||||
Description: The name of the networking stack that
|
||||
these resources are put into.
|
||||
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 'StackName', '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 'StackName', '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 'StackName', 'ECSTaskExecutionRole']]
|
||||
ContainerDefinitions:
|
||||
- Name: !Ref 'ServiceName'
|
||||
Cpu: !Ref 'ContainerCpu'
|
||||
Memory: !Ref 'ContainerMemory'
|
||||
Image: !Ref 'ImageUrl'
|
||||
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 'StackName', 'ClusterName']]
|
||||
LaunchType: FARGATE
|
||||
DeploymentConfiguration:
|
||||
MaximumPercent: 200
|
||||
MinimumHealthyPercent: 50
|
||||
DesiredCount: !Ref 'DesiredCount'
|
||||
NetworkConfiguration:
|
||||
AwsvpcConfiguration:
|
||||
AssignPublicIp: ENABLED
|
||||
SecurityGroups:
|
||||
- Fn::ImportValue:
|
||||
!Join [':', [!Ref 'StackName', 'ECSSecurityGroup']]
|
||||
Subnets:
|
||||
- Fn::ImportValue:
|
||||
!Join [':', [!Ref 'StackName', 'PublicSubnetOne']]
|
||||
- Fn::ImportValue:
|
||||
!Join [':', [!Ref 'StackName', 'PublicSubnetTwo']]
|
||||
TaskDefinition: !Ref 'TaskDefinition'
|
||||
LoadBalancers:
|
||||
- ContainerName: !Ref 'ServiceName'
|
||||
ContainerPort: !Ref 'ContainerPort'
|
||||
TargetGroupArn: !Ref 'TargetGroup'
|
||||
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'
|
||||
101
build-all.sh
Normal file
101
build-all.sh
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
|
||||
MAIN_DIR=$PWD
|
||||
|
||||
build_gradle_module() {
|
||||
MODULE_PATH=$1
|
||||
echo ""
|
||||
echo "+++"
|
||||
echo "+++ BUILDING MODULE $MODULE_PATH"
|
||||
echo "+++"
|
||||
cd $MODULE_PATH && {
|
||||
chmod +x gradlew
|
||||
./gradlew clean build
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo ""
|
||||
echo "+++"
|
||||
echo "+++ BUILDING MODULE $MODULE_PATH FAILED"
|
||||
echo "+++"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "+++"
|
||||
echo "+++ BUILDING MODULE $MODULE_PATH SUCCESSFUL"
|
||||
echo "+++"
|
||||
fi
|
||||
cd $MAIN_DIR
|
||||
}
|
||||
}
|
||||
|
||||
build_maven_module() {
|
||||
MODULE_PATH=$1
|
||||
echo ""
|
||||
echo "+++"
|
||||
echo "+++ BUILDING MODULE $MODULE_PATH"
|
||||
echo "+++"
|
||||
cd $MODULE_PATH && {
|
||||
chmod +x mvnw
|
||||
./mvnw clean package
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo ""
|
||||
echo "+++"
|
||||
echo "+++ BUILDING MODULE $MODULE_PATH FAILED"
|
||||
echo "+++"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "+++"
|
||||
echo "+++ BUILDING MODULE $MODULE_PATH SUCCESSFUL"
|
||||
echo "+++"
|
||||
fi
|
||||
cd $MAIN_DIR
|
||||
}
|
||||
}
|
||||
|
||||
build_maven_module "spring-boot/dependency-injection"
|
||||
build_maven_module "spring-boot/spring-boot-openapi"
|
||||
build_maven_module "spring-boot/data-migration/liquibase"
|
||||
build_gradle_module "spring-boot/boundaries"
|
||||
build_gradle_module "spring-boot/argumentresolver"
|
||||
build_gradle_module "spring-data/spring-data-jdbc-converter"
|
||||
build_gradle_module "solid"
|
||||
build_gradle_module "spring-boot/data-migration/flyway"
|
||||
build_gradle_module "reactive"
|
||||
build_gradle_module "junit/assumptions"
|
||||
build_gradle_module "logging"
|
||||
build_gradle_module "pact/pact-feign-consumer"
|
||||
# currently disabled since the consumer build won't run
|
||||
# build_gradle_module "pact/pact-message-consumer"
|
||||
# build_gradle_module "pact/pact-message-producer"
|
||||
build_gradle_module "pact/pact-spring-provider"
|
||||
build_gradle_module "patterns"
|
||||
build_gradle_module "spring-boot/conditionals"
|
||||
build_gradle_module "spring-boot/configuration"
|
||||
build_gradle_module "spring-boot/mocking"
|
||||
build_gradle_module "spring-boot/modular"
|
||||
build_gradle_module "spring-boot/paging"
|
||||
build_gradle_module "spring-boot/rabbitmq-event-brokering"
|
||||
build_gradle_module "spring-boot/spring-boot-logging"
|
||||
build_gradle_module "spring-boot/spring-boot-testing"
|
||||
build_gradle_module "spring-boot/starter"
|
||||
build_gradle_module "spring-boot/startup"
|
||||
build_gradle_module "spring-boot/static"
|
||||
build_gradle_module "spring-boot/validation"
|
||||
build_gradle_module "spring-boot/profiles"
|
||||
build_gradle_module "spring-boot/password-encoding"
|
||||
build_gradle_module "spring-boot/testcontainers"
|
||||
build_gradle_module "spring-cloud/feign-with-spring-data-rest"
|
||||
build_gradle_module "spring-cloud/sleuth-downstream-service"
|
||||
build_gradle_module "spring-cloud/sleuth-upstream-service"
|
||||
build_gradle_module "spring-cloud/spring-cloud-contract-consumer"
|
||||
build_gradle_module "spring-cloud/spring-cloud-contract-provider"
|
||||
build_gradle_module "spring-data/spring-data-rest-associations"
|
||||
build_gradle_module "spring-data/spring-data-rest-springfox"
|
||||
build_gradle_module "tools/jacoco"
|
||||
|
||||
echo ""
|
||||
echo "+++"
|
||||
echo "+++ ALL MODULES SUCCESSFUL"
|
||||
echo "+++"
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
apply plugin: 'java'
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = 1.8
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@@ -23,3 +23,6 @@ dependencies {
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
|
||||
|
||||
5
logging/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
logging/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip
|
||||
0
gradlew → logging/gradlew
vendored
0
gradlew → logging/gradlew
vendored
84
logging/gradlew.bat
vendored
Normal file
84
logging/gradlew.bat
vendored
Normal 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,27 +0,0 @@
|
||||
package io.reflectoring.logging;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import ch.qos.logback.classic.pattern.LoggerConverter;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
|
||||
public class TruncatedLoggerConverter extends LoggerConverter {
|
||||
|
||||
@Override
|
||||
public String convert(ILoggingEvent event) {
|
||||
String maxLengthString = getFirstOption();
|
||||
int maxLength = Integer.parseInt(maxLengthString);
|
||||
String loggerName = super.convert(event);
|
||||
if (loggerName.length() <= maxLength) {
|
||||
return loggerName + generateSpaces(maxLength - loggerName.length());
|
||||
} else {
|
||||
return "..." + loggerName.substring(loggerName.length() - maxLength + 3);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateSpaces(int count) {
|
||||
return Stream.generate(() -> " ").limit(count).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package io.reflectoring.logging;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import ch.qos.logback.classic.pattern.ClassicConverter;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
|
||||
public class TruncatedThreadConverter extends ClassicConverter {
|
||||
|
||||
@Override
|
||||
public String convert(ILoggingEvent event) {
|
||||
String maxLengthString = getFirstOption();
|
||||
int maxLength = Integer.parseInt(maxLengthString);
|
||||
String threadName = event.getThreadName();
|
||||
|
||||
if (threadName.length() <= maxLength) {
|
||||
return threadName + generateSpaces(maxLength - threadName.length());
|
||||
} else {
|
||||
return "..." + threadName.substring(threadName.length() - maxLength + 3);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateSpaces(int count) {
|
||||
return Stream.generate(() -> " ").limit(count).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
<!-- Appender to log to console -->
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd} | %d{HH:mm:ss.SSS} | %truncatedThread{20} | %5p | %truncatedLogger{25} | %12(ID: %8mdc{id}) | %m%n</pattern>
|
||||
<pattern>%d{yyyy-MM-dd} | %d{HH:mm:ss.SSS} | %-20.20thread | %5p | %-25.25logger{25} | %12(ID: %8mdc{id}) | %m%n</pattern>
|
||||
<charset>utf8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
@@ -4,6 +4,9 @@ This example project shows how to setup an Angular application to use [Pact](htt
|
||||
in order to create Pact files from a consumer test and validate the
|
||||
a consumer against the Pact.
|
||||
|
||||
## Relevant Blog Post
|
||||
[Creating a Consumer-Driven Contract with Angular and Pact](https://reflectoring.io/consumer-driven-contracts-with-angular-and-pact/)
|
||||
|
||||
## Key Files
|
||||
|
||||
* [`user.service.ts`](src/app/user.service.ts): Angular service that calls a REST
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
userservice:
|
||||
ribbon:
|
||||
eureka:
|
||||
enabled: false
|
||||
listOfServers: localhost:8080
|
||||
@@ -1,5 +1,3 @@
|
||||
apply plugin: 'org.springframework.boot'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
@@ -10,18 +8,56 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "au.com.dius.pact" version "3.5.20"
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
version '1.0.0.SNAPSHOT'
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile("org.springframework.boot:spring-boot-starter-data-jpa:${springboot_version}")
|
||||
compile("org.springframework.boot:spring-boot-starter-web:${springboot_version}")
|
||||
compile("org.springframework.cloud:spring-cloud-starter-feign:1.4.1.RELEASE")
|
||||
compile('com.h2database:h2:1.4.196')
|
||||
testCompile('org.codehaus.groovy:groovy-all:2.4.6')
|
||||
testCompile("au.com.dius:pact-jvm-consumer-junit_2.11:3.5.2")
|
||||
testCompile("org.springframework.boot:spring-boot-starter-test:${springboot_version}")
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springcloud_version}"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-data-jpa')
|
||||
compile('org.springframework.boot:spring-boot-starter-web')
|
||||
compile('org.springframework.cloud:spring-cloud-starter-openfeign')
|
||||
compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
|
||||
compile('com.h2database:h2:1.4.196')
|
||||
|
||||
// add jaxb since it's no longer available in Java 11
|
||||
runtime('javax.xml.bind:jaxb-api:2.3.1')
|
||||
|
||||
// add javassist >= 3.23.1-GA since earlier versions are broken in Java 11
|
||||
// see https://github.com/jboss-javassist/javassist/issues/194
|
||||
runtime('org.javassist:javassist:3.23.1-GA')
|
||||
|
||||
testCompile('org.codehaus.groovy:groovy-all:2.4.6')
|
||||
testCompile("au.com.dius:pact-jvm-consumer-junit5_2.12:${pact_version}")
|
||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
||||
testRuntimeOnly( 'org.junit.jupiter:junit-jupiter-engine:5.1.0')
|
||||
}
|
||||
|
||||
pact {
|
||||
publish {
|
||||
pactDirectory = 'target/pacts'
|
||||
pactBrokerUrl = 'TODO'
|
||||
pactBrokerUsername = 'TODO'
|
||||
pactBrokerPassword = 'TODO'
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
springboot_version=1.5.9.RELEASE
|
||||
verifier_version=1.2.2.RELEASE
|
||||
springboot_version=2.0.4.RELEASE
|
||||
springcloud_version=Finchley.SR1
|
||||
pact_version=3.5.20
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip
|
||||
|
||||
@@ -2,10 +2,12 @@ package io.reflectoring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.netflix.feign.EnableFeignClients;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableFeignClients
|
||||
@RibbonClient(name = "userservice", configuration = RibbonConfiguration.class)
|
||||
public class ConsumerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.IRule;
|
||||
import com.netflix.loadbalancer.RandomRule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
public class RibbonConfiguration {
|
||||
|
||||
@Bean
|
||||
public IRule ribbonRule(IClientConfig config) {
|
||||
return new RandomRule();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import org.springframework.cloud.netflix.feign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@@ -1,83 +1,88 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import au.com.dius.pact.consumer.Pact;
|
||||
import au.com.dius.pact.consumer.PactProviderRuleMk2;
|
||||
import au.com.dius.pact.consumer.PactVerification;
|
||||
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
|
||||
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
|
||||
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
|
||||
import au.com.dius.pact.consumer.junit5.PactTestFor;
|
||||
import au.com.dius.pact.model.RequestResponsePact;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
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.junit4.SpringRunner;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(properties = {
|
||||
// overriding provider address
|
||||
"userservice.ribbon.listOfServers: localhost:8888"
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
@ExtendWith(PactConsumerTestExt.class)
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@PactTestFor(providerName = "userservice", port = "8888")
|
||||
@SpringBootTest({
|
||||
// overriding provider address
|
||||
"userservice.ribbon.listOfServers: localhost:8888"
|
||||
})
|
||||
public class UserServiceConsumerTest {
|
||||
class UserServiceConsumerTest {
|
||||
|
||||
@Rule
|
||||
public PactProviderRuleMk2 stubProvider = new PactProviderRuleMk2("userservice", "localhost", 8888, this);
|
||||
@Autowired
|
||||
private UserClient userClient;
|
||||
|
||||
@Autowired
|
||||
private UserClient userClient;
|
||||
@Pact(state = "provider accepts a new person", provider = "userservice", consumer = "userclient")
|
||||
RequestResponsePact createPersonPact(PactDslWithProvider builder) {
|
||||
|
||||
@Pact(state = "provider accepts a new person", provider = "userservice", consumer = "userclient")
|
||||
public RequestResponsePact createPersonPact(PactDslWithProvider builder) {
|
||||
return builder
|
||||
.given("provider accepts a new person")
|
||||
.uponReceiving("a request to POST a person")
|
||||
.path("/user-service/users")
|
||||
.method("POST")
|
||||
.willRespondWith()
|
||||
.status(201)
|
||||
.matchHeader("Content-Type", "application/json")
|
||||
.body(new PactDslJsonBody()
|
||||
.integerType("id", 42))
|
||||
.toPact();
|
||||
}
|
||||
// @formatter:off
|
||||
return builder
|
||||
.given("provider accepts a new person")
|
||||
.uponReceiving("a request to POST a person")
|
||||
.path("/user-service/users")
|
||||
.method("POST")
|
||||
.willRespondWith()
|
||||
.status(201)
|
||||
.matchHeader("Content-Type", "application/json")
|
||||
.body(new PactDslJsonBody()
|
||||
.integerType("id", 42))
|
||||
.toPact();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Pact(state = "person 42 exists", provider = "userservice", consumer = "userclient")
|
||||
public RequestResponsePact updatePersonPact(PactDslWithProvider builder) {
|
||||
return builder
|
||||
.given("person 42 exists")
|
||||
.uponReceiving("a request to PUT a person")
|
||||
.path("/user-service/users/42")
|
||||
.method("PUT")
|
||||
.willRespondWith()
|
||||
.status(200)
|
||||
.matchHeader("Content-Type", "application/json")
|
||||
.body(new PactDslJsonBody()
|
||||
.stringType("firstName", "Zaphod")
|
||||
.stringType("lastName", "Beeblebrox"))
|
||||
.toPact();
|
||||
}
|
||||
@Pact(state = "person 42 exists", provider = "userservice", consumer = "userclient")
|
||||
RequestResponsePact updatePersonPact(PactDslWithProvider builder) {
|
||||
// @formatter:off
|
||||
return builder
|
||||
.given("person 42 exists")
|
||||
.uponReceiving("a request to PUT a person")
|
||||
.path("/user-service/users/42")
|
||||
.method("PUT")
|
||||
.willRespondWith()
|
||||
.status(200)
|
||||
.matchHeader("Content-Type", "application/json")
|
||||
.body(new PactDslJsonBody()
|
||||
.stringType("firstName", "Zaphod")
|
||||
.stringType("lastName", "Beeblebrox"))
|
||||
.toPact();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@PactVerification(fragment = "createPersonPact")
|
||||
public void verifyCreatePersonPact() {
|
||||
User user = new User();
|
||||
user.setFirstName("Zaphod");
|
||||
user.setLastName("Beeblebrox");
|
||||
IdObject id = userClient.createUser(user);
|
||||
assertThat(id.getId()).isEqualTo(42);
|
||||
}
|
||||
@Test
|
||||
@PactTestFor(pactMethod = "createPersonPact")
|
||||
void verifyCreatePersonPact() {
|
||||
User user = new User();
|
||||
user.setFirstName("Zaphod");
|
||||
user.setLastName("Beeblebrox");
|
||||
IdObject id = userClient.createUser(user);
|
||||
assertThat(id.getId()).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
@PactVerification(fragment = "updatePersonPact")
|
||||
public void verifyUpdatePersonPact() {
|
||||
User user = new User();
|
||||
user.setFirstName("Zaphod");
|
||||
user.setLastName("Beeblebrox");
|
||||
User updatedUser = userClient.updateUser(42L, user);
|
||||
assertThat(updatedUser.getFirstName()).isEqualTo("Zaphod");
|
||||
assertThat(updatedUser.getLastName()).isEqualTo("Beeblebrox");
|
||||
}
|
||||
@Test
|
||||
@PactTestFor(pactMethod = "updatePersonPact")
|
||||
void verifyUpdatePersonPact() {
|
||||
User user = new User();
|
||||
user.setFirstName("Zaphod");
|
||||
user.setLastName("Beeblebrox");
|
||||
User updatedUser = userClient.updateUser(42L, user);
|
||||
assertThat(updatedUser.getFirstName()).isEqualTo("Zaphod");
|
||||
assertThat(updatedUser.getLastName()).isEqualTo("Beeblebrox");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"provider": {
|
||||
"name": "userservice"
|
||||
},
|
||||
"consumer": {
|
||||
"name": "userclient"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "a request to PUT a person",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"path": "/user-service/users/42"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"firstName": "Zaphod",
|
||||
"lastName": "Beeblebrox"
|
||||
},
|
||||
"matchingRules": {
|
||||
"body": {
|
||||
"$.firstName": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.lastName": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"providerStates": [
|
||||
{
|
||||
"name": "person 42 exists"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "a request to POST a person",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"path": "/user-service/users"
|
||||
},
|
||||
"response": {
|
||||
"status": 201,
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"id": 42
|
||||
},
|
||||
"matchingRules": {
|
||||
"header": {
|
||||
"Content-Type": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "regex",
|
||||
"regex": "application/json"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"$.id": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "integer"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"providerStates": [
|
||||
{
|
||||
"name": "provider accepts a new person"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pactSpecification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.20"
|
||||
}
|
||||
}
|
||||
}
|
||||
25
pact/pact-message-consumer/.gitignore
vendored
Normal file
25
pact/pact-message-consumer/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
.gradle
|
||||
/build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
nbproject/private/
|
||||
build/
|
||||
nbbuild/
|
||||
dist/
|
||||
nbdist/
|
||||
.nb-gradle/
|
||||
7
pact/pact-message-consumer/README.md
Normal file
7
pact/pact-message-consumer/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Consumer-Driven-Contract Test for a Spring Boot Message Consumer
|
||||
|
||||
This module shows how to use Pact to implement a contract test for a message provider.
|
||||
|
||||
## Companion Articles
|
||||
[Testing a Spring Message Producer and Consumer against a Contract with Pact](https://reflectoring.io/cdc-pact-messages/)
|
||||
|
||||
1
pact/pact-message-consumer/application.yml
Normal file
1
pact/pact-message-consumer/application.yml
Normal file
@@ -0,0 +1 @@
|
||||
spring.rabbitmq.connection-timeout=10
|
||||
43
pact/pact-message-consumer/build.gradle
Normal file
43
pact/pact-message-consumer/build.gradle
Normal file
@@ -0,0 +1,43 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot_version}")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-web')
|
||||
compile('org.springframework.boot:spring-boot-starter-amqp')
|
||||
compile('com.h2database:h2:1.4.196')
|
||||
compileOnly('org.projectlombok:lombok:1.18.2')
|
||||
|
||||
// add jaxb since it's no longer available in Java 11
|
||||
runtime('javax.xml.bind:jaxb-api:2.3.1')
|
||||
|
||||
// add javassist >= 3.23.1-GA since earlier versions are broken in Java 11
|
||||
// see https://github.com/jboss-javassist/javassist/issues/194
|
||||
runtime('org.javassist:javassist:3.23.1-GA')
|
||||
|
||||
testCompile("au.com.dius:pact-jvm-consumer-junit_2.12:${pact_version}")
|
||||
testCompile("au.com.dius:pact-jvm-consumer-groovy_2.12:${pact_version}")
|
||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
||||
}
|
||||
|
||||
bootRun {
|
||||
jvmArgs = ["-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"]
|
||||
}
|
||||
2
pact/pact-message-consumer/gradle.properties
Normal file
2
pact/pact-message-consumer/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
springboot_version=2.0.4.RELEASE
|
||||
pact_version=3.5.20
|
||||
5
pact/pact-message-consumer/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
pact/pact-message-consumer/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
|
||||
84
pact/pact-message-consumer/gradlew.bat
vendored
Normal file
84
pact/pact-message-consumer/gradlew.bat
vendored
Normal 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
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableRabbit
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.reflectoring;
|
||||
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MessageConsumer {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MessageConsumer.class);
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public MessageConsumer(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
public void consumeStringMessage(String messageString) throws IOException {
|
||||
logger.info("Consuming message '{}'", messageString);
|
||||
UserCreatedMessage message = objectMapper.readValue(messageString, UserCreatedMessage.class);
|
||||
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||
Set<ConstraintViolation<UserCreatedMessage>> violations = validator.validate(message);
|
||||
if(!violations.isEmpty()){
|
||||
throw new ConstraintViolationException(violations);
|
||||
}
|
||||
// pass message into business use case
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.amqp.core.Binding;
|
||||
import org.springframework.amqp.core.BindingBuilder;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class MessageConsumerConfiguration {
|
||||
|
||||
private static final String QUEUE_NAME = "myQueue";
|
||||
|
||||
private static final String EXCHANGE_NAME = "myExchange";
|
||||
|
||||
@Bean
|
||||
public TopicExchange receiverExchange() {
|
||||
return new TopicExchange(EXCHANGE_NAME);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue queue() {
|
||||
return new Queue(QUEUE_NAME);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding(Queue eventReceivingQueue, TopicExchange receiverExchange) {
|
||||
return BindingBuilder
|
||||
.bind(eventReceivingQueue)
|
||||
.to(receiverExchange)
|
||||
.with("*.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
|
||||
MessageListenerAdapter listenerAdapter) {
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
|
||||
container.setConsumerStartTimeout(1000); // we don't want to wait in this example project
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
container.setQueueNames(QUEUE_NAME);
|
||||
container.setMessageListener(listenerAdapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageListenerAdapter listenerAdapter(MessageConsumer messageConsumer) {
|
||||
return new MessageListenerAdapter(messageConsumer, "consumeStringMessage");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageConsumer eventReceiver(ObjectMapper objectMapper) {
|
||||
return new MessageConsumer(objectMapper);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class User {
|
||||
|
||||
@NotNull
|
||||
private long id;
|
||||
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UserCreatedMessage {
|
||||
|
||||
@NotNull
|
||||
private String messageUuid;
|
||||
|
||||
@NotNull
|
||||
private User user;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import au.com.dius.pact.consumer.MessagePactBuilder;
|
||||
import au.com.dius.pact.consumer.MessagePactProviderRule;
|
||||
import au.com.dius.pact.consumer.Pact;
|
||||
import au.com.dius.pact.consumer.PactVerification;
|
||||
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
|
||||
import au.com.dius.pact.model.v3.messaging.MessagePact;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MessageConsumerTest {
|
||||
|
||||
@Rule
|
||||
public MessagePactProviderRule mockProvider = new MessagePactProviderRule(this);
|
||||
private byte[] currentMessage;
|
||||
|
||||
@Autowired
|
||||
private MessageConsumer messageConsumer;
|
||||
|
||||
@Pact(provider = "userservice", consumer = "userclient")
|
||||
public MessagePact userCreatedMessagePact(MessagePactBuilder builder) {
|
||||
PactDslJsonBody body = new PactDslJsonBody();
|
||||
body.stringType("messageUuid");
|
||||
body.object("user")
|
||||
.numberType("id", 42L)
|
||||
.stringType("name", "Zaphod Beeblebrox")
|
||||
.closeObject();
|
||||
|
||||
// @formatter:off
|
||||
return builder
|
||||
.expectsToReceive("a user created message")
|
||||
.withContent(body)
|
||||
.toPact();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
@PactVerification("userCreatedMessagePact")
|
||||
public void verifyCreatePersonPact() throws IOException {
|
||||
messageConsumer.consumeStringMessage(new String(this.currentMessage));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by the Pact framework.
|
||||
*/
|
||||
public void setMessage(byte[] message) {
|
||||
this.currentMessage = message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"consumer": {
|
||||
"name": "userclient"
|
||||
},
|
||||
"provider": {
|
||||
"name": "userservice"
|
||||
},
|
||||
"messages": [
|
||||
{
|
||||
"description": "a user created message",
|
||||
"metaData": {
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
},
|
||||
"contents": {
|
||||
"messageUuid": "string",
|
||||
"user": {
|
||||
"id": 42,
|
||||
"name": "Zaphod Beeblebrox"
|
||||
}
|
||||
},
|
||||
"matchingRules": {
|
||||
"body": {
|
||||
"$.messageUuid": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.user.id": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "number"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
},
|
||||
"$.user.name": {
|
||||
"matchers": [
|
||||
{
|
||||
"match": "type"
|
||||
}
|
||||
],
|
||||
"combine": "AND"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pactSpecification": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"pact-jvm": {
|
||||
"version": "3.5.20"
|
||||
}
|
||||
}
|
||||
}
|
||||
25
pact/pact-message-producer/.gitignore
vendored
Normal file
25
pact/pact-message-producer/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
.gradle
|
||||
/build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
nbproject/private/
|
||||
build/
|
||||
nbbuild/
|
||||
dist/
|
||||
nbdist/
|
||||
.nb-gradle/
|
||||
3
pact/pact-message-producer/README.md
Normal file
3
pact/pact-message-producer/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Consumer-Driven-Contract Test for a Spring Boot Message Provider
|
||||
|
||||
This module shows how to use Pact to implement a contract test for a message provider.
|
||||
46
pact/pact-message-producer/build.gradle
Normal file
46
pact/pact-message-producer/build.gradle
Normal file
@@ -0,0 +1,46 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot_version}")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = 11
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-web')
|
||||
compile('org.springframework.boot:spring-boot-starter-amqp')
|
||||
compile('com.h2database:h2:1.4.196')
|
||||
compileOnly('org.projectlombok:lombok:1.18.2')
|
||||
|
||||
// add jaxb since it's no longer available in Java 11
|
||||
runtime('javax.xml.bind:jaxb-api:2.3.1')
|
||||
|
||||
// add javassist >= 3.23.1-GA since earlier versions are broken in Java 11
|
||||
// see https://github.com/jboss-javassist/javassist/issues/194
|
||||
runtime('org.javassist:javassist:3.23.1-GA')
|
||||
|
||||
// overriding bytebuddy to newer version that supports Java 11
|
||||
// see https://github.com/raphw/byte-buddy/issues/428
|
||||
testCompile('net.bytebuddy:byte-buddy:1.9.12')
|
||||
|
||||
testCompile("au.com.dius:pact-jvm-provider-junit_2.12:${pact_version}")
|
||||
testCompile('org.springframework.boot:spring-boot-starter-test')
|
||||
}
|
||||
|
||||
bootRun {
|
||||
jvmArgs = ["-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"]
|
||||
}
|
||||
2
pact/pact-message-producer/gradle.properties
Normal file
2
pact/pact-message-producer/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
springboot_version=2.0.4.RELEASE
|
||||
pact_version=3.5.20
|
||||
BIN
pact/pact-message-producer/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
pact/pact-message-producer/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
pact/pact-message-producer/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
pact/pact-message-producer/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-bin.zip
|
||||
172
pact/pact-message-producer/gradlew
vendored
Normal file
172
pact/pact-message-producer/gradlew
vendored
Normal 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
pact/pact-message-producer/gradlew.bat
vendored
Normal file
84
pact/pact-message-producer/gradlew.bat
vendored
Normal 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
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Takes a {@link UserCreatedMessage}, converts it to a {@link String} and sends it to be published.
|
||||
*/
|
||||
class MessageProducer {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(MessageProducer.class);
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private MessagePublisher messagePublisher;
|
||||
|
||||
MessageProducer(ObjectMapper objectMapper, MessagePublisher messagePublisher) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.messagePublisher = messagePublisher;
|
||||
}
|
||||
|
||||
void produceUserCreatedMessage(UserCreatedMessage message) throws IOException {
|
||||
String stringMessage = objectMapper.writeValueAsString(message);
|
||||
messagePublisher.publishMessage(stringMessage, "user.created");
|
||||
logger.info("Published message '{}'", stringMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
class MessageProviderConfiguration {
|
||||
|
||||
@Bean
|
||||
TopicExchange topicExchange() {
|
||||
return new TopicExchange("myExchange");
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
MessageProducer messageProvider(ObjectMapper objectMapper, MessagePublisher publisher) {
|
||||
return new MessageProducer(objectMapper, publisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessagePublisher messagePublisher(RabbitTemplate rabbitTemplate, TopicExchange topicExchange) {
|
||||
return new MessagePublisher(rabbitTemplate, topicExchange);
|
||||
}
|
||||
|
||||
@Bean
|
||||
SendMessageJob job(MessageProducer messageProducer) {
|
||||
return new SendMessageJob(messageProducer);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
|
||||
/**
|
||||
* Publishes a String message to RabbitMQ.
|
||||
*/
|
||||
class MessagePublisher {
|
||||
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
private TopicExchange topicExchange;
|
||||
|
||||
MessagePublisher(RabbitTemplate rabbitTemplate, TopicExchange topicExchange) {
|
||||
this.rabbitTemplate = rabbitTemplate;
|
||||
this.topicExchange = topicExchange;
|
||||
}
|
||||
|
||||
void publishMessage(String message, String routingKey) {
|
||||
rabbitTemplate.convertAndSend(topicExchange.getName(), routingKey, message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
class SendMessageJob {
|
||||
|
||||
private Random random = new Random();
|
||||
|
||||
private MessageProducer messageProducer;
|
||||
|
||||
SendMessageJob(MessageProducer messageProducer) {
|
||||
this.messageProducer = messageProducer;
|
||||
}
|
||||
|
||||
/**
|
||||
* This scheduled job simulates the "real" business logic that should produce messages.
|
||||
*/
|
||||
@Scheduled(fixedDelay = 1000)
|
||||
void sendUserCreatedMessage() {
|
||||
try {
|
||||
UserCreatedMessage userCreatedMessage = UserCreatedMessage.builder()
|
||||
.messageUuid(UUID.randomUUID().toString())
|
||||
.user(User.builder()
|
||||
.id(random.nextLong())
|
||||
.name("Zaphpod Beeblebrox")
|
||||
.build())
|
||||
.build();
|
||||
messageProducer.produceUserCreatedMessage(userCreatedMessage);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
class User {
|
||||
|
||||
@NotNull
|
||||
private long id;
|
||||
|
||||
@NotNull
|
||||
private String name;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
class UserCreatedMessage {
|
||||
|
||||
@NotNull
|
||||
private String messageUuid;
|
||||
|
||||
@NotNull
|
||||
private User user;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import au.com.dius.pact.model.Interaction;
|
||||
import au.com.dius.pact.model.ProviderState;
|
||||
import au.com.dius.pact.provider.ConsumerInfo;
|
||||
import au.com.dius.pact.provider.ProviderInfo;
|
||||
import au.com.dius.pact.provider.ProviderVerifier;
|
||||
import au.com.dius.pact.provider.junit.target.AmqpTarget;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Custom implementation of {@link AmqpTarget} since with {@link AmqpTarget}
|
||||
* I had classpath troubles (see https://github.com/DiUS/pact-jvm/issues/763).
|
||||
* Use at own risk, since the implementation is not complete and may behave
|
||||
* a little different that the original class.
|
||||
*/
|
||||
public class CustomAmqpTarget extends AmqpTarget {
|
||||
|
||||
public CustomAmqpTarget(List<String> packagesToScan) {
|
||||
super(packagesToScan);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected ProviderVerifier setupVerifier(Interaction interaction, ProviderInfo provider, ConsumerInfo consumer) {
|
||||
ProviderVerifier verifier = new CustomProviderVerifier(getPackagesToScan());
|
||||
setupReporters(verifier, provider.getName(), interaction.getDescription());
|
||||
verifier.initialiseReporters(provider);
|
||||
verifier.reportVerificationForConsumer(consumer, provider);
|
||||
|
||||
if (!interaction.getProviderStates().isEmpty()) {
|
||||
for (ProviderState state : interaction.getProviderStates()) {
|
||||
verifier.reportStateForInteraction(state.getName(), provider, consumer, true);
|
||||
}
|
||||
}
|
||||
verifier.reportInteractionDescription(interaction);
|
||||
return verifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import au.com.dius.pact.model.Interaction;
|
||||
import au.com.dius.pact.model.v3.messaging.Message;
|
||||
import au.com.dius.pact.provider.ConsumerInfo;
|
||||
import au.com.dius.pact.provider.PactVerifyProvider;
|
||||
import au.com.dius.pact.provider.ProviderInfo;
|
||||
import au.com.dius.pact.provider.ProviderVerifier;
|
||||
import org.reflections.Reflections;
|
||||
import org.reflections.scanners.MethodAnnotationsScanner;
|
||||
import org.reflections.util.ConfigurationBuilder;
|
||||
|
||||
public class CustomProviderVerifier extends ProviderVerifier {
|
||||
|
||||
private List<String> packagesToScan;
|
||||
|
||||
public CustomProviderVerifier(List<String> packagesToScan) {
|
||||
this.packagesToScan = packagesToScan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyResponseByInvokingProviderMethods(ProviderInfo providerInfo, ConsumerInfo consumer,
|
||||
Object interaction, String interactionMessage, Map failures) {
|
||||
try {
|
||||
|
||||
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder()
|
||||
.setScanners(new MethodAnnotationsScanner())
|
||||
.forPackages(packagesToScan.toArray(new String[]{}));
|
||||
|
||||
Reflections reflections = new Reflections(configurationBuilder);
|
||||
Set<Method> methodsAnnotatedWith = reflections.getMethodsAnnotatedWith(PactVerifyProvider.class);
|
||||
Set<Method> providerMethods = methodsAnnotatedWith.stream()
|
||||
.filter(m -> {
|
||||
PactVerifyProvider annotation = m.getAnnotation(PactVerifyProvider.class);
|
||||
return annotation.value().equals(((Interaction)interaction).getDescription());
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (providerMethods.isEmpty()) {
|
||||
throw new RuntimeException("No annotated methods were found for interaction " +
|
||||
"'${interaction.description}'. You need to provide a method annotated with " +
|
||||
"@PactVerifyProvider(\"${interaction.description}\") that returns the message contents.");
|
||||
} else {
|
||||
if (interaction instanceof Message) {
|
||||
verifyMessagePact(providerMethods, (Message) interaction, interactionMessage, failures);
|
||||
} else {
|
||||
throw new RuntimeException("only supports Message interactions!");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("verification failed", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.reflectoring;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import au.com.dius.pact.provider.PactVerifyProvider;
|
||||
import au.com.dius.pact.provider.junit.PactRunner;
|
||||
import au.com.dius.pact.provider.junit.Provider;
|
||||
import au.com.dius.pact.provider.junit.loader.PactFolder;
|
||||
import au.com.dius.pact.provider.junit.target.Target;
|
||||
import au.com.dius.pact.provider.junit.target.TestTarget;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(PactRunner.class)
|
||||
@Provider("userservice")
|
||||
@PactFolder("../pact-message-consumer/target/pacts")
|
||||
public class UserCreatedMessageProviderTest {
|
||||
|
||||
@TestTarget
|
||||
public final Target target = new CustomAmqpTarget(Collections.singletonList("io.reflectoring"));
|
||||
|
||||
private MessagePublisher publisher = Mockito.mock(MessagePublisher.class);
|
||||
|
||||
private MessageProducer messageProducer = new MessageProducer(new ObjectMapper(), publisher);
|
||||
|
||||
@PactVerifyProvider("a user created message")
|
||||
public String verifyUserCreatedMessage() throws IOException {
|
||||
// given
|
||||
doNothing().when(publisher).publishMessage(any(String.class), eq("user.created"));
|
||||
|
||||
// when
|
||||
UserCreatedMessage message = UserCreatedMessage.builder()
|
||||
.messageUuid(UUID.randomUUID().toString())
|
||||
.user(User.builder()
|
||||
.id(42L)
|
||||
.name("Zaphod Beeblebrox")
|
||||
.build())
|
||||
.build();
|
||||
messageProducer.produceUserCreatedMessage(message);
|
||||
|
||||
// then
|
||||
ArgumentCaptor<String> messageCapture = ArgumentCaptor.forClass(String.class);
|
||||
verify(publisher, times(1)).publishMessage(messageCapture.capture(), eq("user.created"));
|
||||
|
||||
// returning the message
|
||||
return messageCapture.getValue();
|
||||
}
|
||||
}
|
||||
24
pact/pact-node-messages/.gitignore
vendored
Normal file
24
pact/pact-node-messages/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.nyc_output/*
|
||||
coverage/*
|
||||
2543
pact/pact-node-messages/package-lock.json
generated
Normal file
2543
pact/pact-node-messages/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user