Compare commits
313 Commits
logging-fo
...
itsLucario
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
339a0db992 | ||
|
|
8efc1d139b | ||
|
|
ef9ad9a6fd | ||
|
|
432f4ea3bc | ||
|
|
9d875fd412 | ||
|
|
251acca5d5 | ||
|
|
e4c78bc9fa | ||
|
|
75fead124d | ||
|
|
155521ea11 | ||
|
|
81355cd289 | ||
|
|
88fd0640ad | ||
|
|
9904d7206e | ||
|
|
e552dd3506 | ||
|
|
568fd5d7aa | ||
|
|
9a2440a58a | ||
|
|
d4458e88ad | ||
|
|
079635b3ad | ||
|
|
035b9f7020 | ||
|
|
457daf5fc1 | ||
|
|
a64c2521de | ||
|
|
791a603f34 | ||
|
|
8729639907 | ||
|
|
ec9992c0ba | ||
|
|
4f5a370999 | ||
|
|
a856ef260b | ||
|
|
7613384396 | ||
|
|
fd484488ca | ||
|
|
b2a9eea9e2 | ||
|
|
58be70d34a | ||
|
|
97c639f902 | ||
|
|
a14c65566a | ||
|
|
6b66a6e980 | ||
|
|
a18d01bd4b | ||
|
|
f1b64604c0 | ||
|
|
7c3be096f1 | ||
|
|
d2ce081264 | ||
|
|
3c6895b295 | ||
|
|
2395a605b4 | ||
|
|
6c86c730c5 | ||
|
|
0815698aff | ||
|
|
4e0ede6fb8 | ||
|
|
4de6c17ae1 | ||
|
|
fa53509c36 | ||
|
|
2273d59166 | ||
|
|
2a82919aa1 | ||
|
|
60f6c7e585 | ||
|
|
31c09ae8dc | ||
|
|
07b2572541 | ||
|
|
7021c7ddea | ||
|
|
8c9f67f2f8 | ||
|
|
96a58ce851 | ||
|
|
5e0f15e4cc | ||
|
|
b8c0923f23 | ||
|
|
2e3cdcec99 | ||
|
|
0deff1fb44 | ||
|
|
34c76bb565 | ||
|
|
92b3faa23c | ||
|
|
430588dbdc | ||
|
|
786b98964c | ||
|
|
fad637580c | ||
|
|
e7f12e4b92 | ||
|
|
de375d2c89 | ||
|
|
fe79d77b62 | ||
|
|
7cb1d952b8 | ||
|
|
df07d9c308 | ||
|
|
97bf254d85 | ||
|
|
381fe85e39 | ||
|
|
a418f21ff1 | ||
|
|
f723e5f1f4 | ||
|
|
8080fc083e | ||
|
|
e1f6de8bd3 | ||
|
|
c36bedd679 | ||
|
|
735d80d72f | ||
|
|
eff3c3c935 | ||
|
|
94f88c4489 | ||
|
|
3eb97d81ec | ||
|
|
27e1428488 | ||
|
|
7b63198a08 | ||
|
|
45c947ddc1 | ||
|
|
e41458b7cd | ||
|
|
03ab12c64b | ||
|
|
f183c92661 | ||
|
|
109dacd121 | ||
|
|
0bff861d25 | ||
|
|
51be8954a9 | ||
|
|
4bd0201900 | ||
|
|
bb7ac442b1 | ||
|
|
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 |
16
.github/workflows/ci.yml
vendored
Normal file
16
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: "Checkout sources"
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
- name: "Setup Java"
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 13
|
||||||
|
- name: "Build all modules"
|
||||||
|
run: chmod 755 build-all.sh && ./build-all.sh
|
||||||
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:
|
before_install:
|
||||||
- chmod +x gradlew
|
- chmod +x build-all.sh
|
||||||
- |
|
- |
|
||||||
if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(.md)|^(LICENSE)'
|
if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(.md)|^(LICENSE)'
|
||||||
then
|
then
|
||||||
@@ -7,7 +7,12 @@ before_install:
|
|||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
install: skip
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./build-all.sh
|
||||||
|
|
||||||
language: java
|
language: java
|
||||||
|
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk13
|
||||||
19
README.md
19
README.md
@@ -1,8 +1,21 @@
|
|||||||
# Example Code Repository
|
# Example Code Repository
|
||||||
|
|
||||||
[](https://travis-ci.org/thombergs/code-examples)
|
[](https://github.com/thombergs/code-examples/actions?query=workflow%3ACI)
|
||||||
|
|
||||||
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).
|
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,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.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 (v4)!";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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.
@@ -1,6 +1,5 @@
|
|||||||
#Sun Jul 30 16:58:54 CEST 2017
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-all.zip
|
|
||||||
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'
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Overview
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Companion Blog Post
|
||||||
|
|
||||||
|
[The AWS Journey Part 4: Zero-Downtime Deployment with CloudFormation and ECS](https://reflectoring.io/aws-cloudformation-ecs-deployment/)
|
||||||
|
|
||||||
18
aws/cloudformation/ecs-zero-downtime-deployment/create-change-set.sh
Executable file
18
aws/cloudformation/ecs-zero-downtime-deployment/create-change-set.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
# Turning off the AWS pager so that the CLI doesn't open an editor for each command result
|
||||||
|
export AWS_PAGER=""
|
||||||
|
|
||||||
|
aws cloudformation create-change-set \
|
||||||
|
--change-set-name update-reflectoring-ecs-zero-downtime-deployment-service \
|
||||||
|
--stack-name reflectoring-ecs-zero-downtime-deployment-service \
|
||||||
|
--use-previous-template \
|
||||||
|
--parameters \
|
||||||
|
ParameterKey=StackName,ParameterValue=reflectoring-ecs-zero-downtime-deployment-network \
|
||||||
|
ParameterKey=ServiceName,ParameterValue=reflectoring-hello-world \
|
||||||
|
ParameterKey=ImageUrl,ParameterValue=docker.io/reflectoring/aws-hello-world:v4 \
|
||||||
|
ParameterKey=ContainerPort,ParameterValue=8080 \
|
||||||
|
ParameterKey=HealthCheckPath,ParameterValue=/hello \
|
||||||
|
ParameterKey=HealthCheckIntervalSeconds,ParameterValue=90
|
||||||
|
|
||||||
|
aws cloudformation describe-change-set \
|
||||||
|
--stack-name reflectoring-ecs-zero-downtime-deployment-service \
|
||||||
|
--change-set-name update-reflectoring-ecs-zero-downtime-deployment-service
|
||||||
22
aws/cloudformation/ecs-zero-downtime-deployment/create.sh
Executable file
22
aws/cloudformation/ecs-zero-downtime-deployment/create.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
# Turning off the AWS pager so that the CLI doesn't open an editor for each command result
|
||||||
|
export AWS_PAGER=""
|
||||||
|
|
||||||
|
aws cloudformation create-stack \
|
||||||
|
--stack-name reflectoring-ecs-zero-downtime-deployment-network \
|
||||||
|
--template-body file://network.yml \
|
||||||
|
--capabilities CAPABILITY_IAM
|
||||||
|
|
||||||
|
aws cloudformation wait stack-create-complete --stack-name reflectoring-ecs-zero-downtime-deployment-network
|
||||||
|
|
||||||
|
aws cloudformation create-stack \
|
||||||
|
--stack-name reflectoring-ecs-zero-downtime-deployment-service \
|
||||||
|
--template-body file://service.yml \
|
||||||
|
--parameters \
|
||||||
|
ParameterKey=StackName,ParameterValue=reflectoring-ecs-zero-downtime-deployment-network \
|
||||||
|
ParameterKey=ServiceName,ParameterValue=reflectoring-hello-world \
|
||||||
|
ParameterKey=ImageUrl,ParameterValue=docker.io/reflectoring/aws-hello-world:v3 \
|
||||||
|
ParameterKey=ContainerPort,ParameterValue=8080 \
|
||||||
|
ParameterKey=HealthCheckPath,ParameterValue=/hello \
|
||||||
|
ParameterKey=HealthCheckIntervalSeconds,ParameterValue=90
|
||||||
|
|
||||||
|
aws cloudformation wait stack-create-complete --stack-name reflectoring-ecs-zero-downtime-deployment-service
|
||||||
8
aws/cloudformation/ecs-zero-downtime-deployment/delete.sh
Executable file
8
aws/cloudformation/ecs-zero-downtime-deployment/delete.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
# Turning off the AWS pager so that the CLI doesn't open an editor for each command result
|
||||||
|
export AWS_PAGER=""
|
||||||
|
|
||||||
|
aws cloudformation delete-stack --stack-name reflectoring-ecs-zero-downtime-deployment-service
|
||||||
|
aws cloudformation wait stack-delete-complete --stack-name reflectoring-ecs-zero-downtime-deployment-service
|
||||||
|
|
||||||
|
aws cloudformation delete-stack --stack-name reflectoring-ecs-zero-downtime-deployment-network
|
||||||
|
aws cloudformation wait stack-delete-complete --stack-name reflectoring-ecs-zero-downtime-deployment-network
|
||||||
@@ -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 |
8
aws/cloudformation/ecs-zero-downtime-deployment/execute-change-set.sh
Executable file
8
aws/cloudformation/ecs-zero-downtime-deployment/execute-change-set.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
# Turning off the AWS pager so that the CLI doesn't open an editor for each command result
|
||||||
|
export AWS_PAGER=""
|
||||||
|
|
||||||
|
aws cloudformation execute-change-set \
|
||||||
|
--stack-name reflectoring-ecs-zero-downtime-deployment-service \
|
||||||
|
--change-set-name update-reflectoring-ecs-zero-downtime-deployment-service
|
||||||
|
|
||||||
|
aws cloudformation wait stack-update-complete --stack-name reflectoring-ecs-zero-downtime-deployment-service
|
||||||
247
aws/cloudformation/ecs-zero-downtime-deployment/network.yml
Normal file
247
aws/cloudformation/ecs-zero-downtime-deployment/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-zero-downtime-deployment/service.yml
Normal file
139
aws/cloudformation/ecs-zero-downtime-deployment/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'
|
||||||
109
aws/cloudformation/ecs-zero-downtime-deployment/task.yml
Normal file
109
aws/cloudformation/ecs-zero-downtime-deployment/task.yml
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
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'
|
||||||
17
aws/cloudformation/ecs-zero-downtime-deployment/update.sh
Executable file
17
aws/cloudformation/ecs-zero-downtime-deployment/update.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
# Turning off the AWS pager so that the CLI doesn't open an editor for each command result
|
||||||
|
export AWS_PAGER=""
|
||||||
|
|
||||||
|
IMAGE_URL=$1
|
||||||
|
|
||||||
|
aws cloudformation update-stack \
|
||||||
|
--stack-name reflectoring-ecs-zero-downtime-deployment-service \
|
||||||
|
--use-previous-template \
|
||||||
|
--parameters \
|
||||||
|
ParameterKey=StackName,ParameterValue=reflectoring-ecs-zero-downtime-deployment-network \
|
||||||
|
ParameterKey=ServiceName,ParameterValue=reflectoring-hello-world \
|
||||||
|
ParameterKey=ImageUrl,ParameterValue=$IMAGE_URL \
|
||||||
|
ParameterKey=ContainerPort,ParameterValue=8080 \
|
||||||
|
ParameterKey=HealthCheckPath,ParameterValue=/hello \
|
||||||
|
ParameterKey=HealthCheckIntervalSeconds,ParameterValue=90
|
||||||
|
|
||||||
|
aws cloudformation wait stack-update-complete --stack-name reflectoring-ecs-zero-downtime-deployment-service
|
||||||
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
|
||||||
|
|
||||||
|
[The AWS Journey Part 3: Connecting a Spring Boot Application to an RDS Instance with CloudFormation](https://reflectoring.io/aws-cloudformation-rds/)
|
||||||
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>
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 32 KiB |
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'
|
||||||
137
build-all.sh
Executable file
137
build-all.sh
Executable file
@@ -0,0 +1,137 @@
|
|||||||
|
#!/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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_gradle_task() {
|
||||||
|
MODULE_PATH=$1
|
||||||
|
TASK_NAME=$2
|
||||||
|
echo ""
|
||||||
|
echo "+++"
|
||||||
|
echo "+++ RUNNING GRADLE TASK $MODULE_PATH : $TASK_NAME"
|
||||||
|
echo "+++"
|
||||||
|
cd $MODULE_PATH && {
|
||||||
|
chmod +x gradlew
|
||||||
|
./gradlew $TASK_NAME
|
||||||
|
if [ $? -ne 0 ]
|
||||||
|
then
|
||||||
|
echo ""
|
||||||
|
echo "+++"
|
||||||
|
echo "+++ GRADLE TASK $MODULE_PATH : $TASK_NAME FAILED"
|
||||||
|
echo "+++"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "+++"
|
||||||
|
echo "+++ GRADLE TASK $MODULE_PATH : $TASK_NAME 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/spring-boot-kafka"
|
||||||
|
build_maven_module "resilience4j/retry"
|
||||||
|
build_maven_module "solid/lsp"
|
||||||
|
run_gradle_task "spring-boot/thymeleaf-vue" "npmInstall"
|
||||||
|
build_gradle_module "spring-boot/thymeleaf-vue"
|
||||||
|
build_gradle_module "spring-boot/spring-boot-springdoc"
|
||||||
|
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-boot/hazelcast/hazelcast-embedded-cache"
|
||||||
|
build_gradle_module "spring-boot/hazelcast/hazelcast-client-server"
|
||||||
|
build_gradle_module "spring-boot/cache"
|
||||||
|
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'
|
apply plugin: 'java'
|
||||||
|
|
||||||
version = '0.0.1-SNAPSHOT'
|
version = '0.0.1-SNAPSHOT'
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 11
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
@@ -23,3 +23,6 @@ dependencies {
|
|||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||||
|
|||||||
0
junit/assumptions/gradlew
vendored
Normal file → Executable file
0
junit/assumptions/gradlew
vendored
Normal file → Executable file
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-6.5-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 to log to console -->
|
||||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<encoder>
|
<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>
|
<charset>utf8</charset>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</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
|
in order to create Pact files from a consumer test and validate the
|
||||||
a consumer against the Pact.
|
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
|
## Key Files
|
||||||
|
|
||||||
* [`user.service.ts`](src/app/user.service.ts): Angular service that calls a REST
|
* [`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 {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
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 {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencyManagement {
|
||||||
compile("org.springframework.boot:spring-boot-starter-data-jpa:${springboot_version}")
|
imports {
|
||||||
compile("org.springframework.boot:spring-boot-starter-web:${springboot_version}")
|
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springcloud_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}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
springboot_version=2.0.4.RELEASE
|
||||||
verifier_version=1.2.2.RELEASE
|
springcloud_version=Finchley.SR1
|
||||||
|
pact_version=3.5.20
|
||||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package io.reflectoring;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
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
|
@SpringBootApplication
|
||||||
@EnableFeignClients
|
@EnableFeignClients
|
||||||
|
@RibbonClient(name = "userservice", configuration = RibbonConfiguration.class)
|
||||||
public class ConsumerApplication {
|
public class ConsumerApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
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;
|
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.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|||||||
@@ -1,83 +1,88 @@
|
|||||||
package io.reflectoring;
|
package io.reflectoring;
|
||||||
|
|
||||||
import au.com.dius.pact.consumer.Pact;
|
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.PactDslJsonBody;
|
||||||
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
|
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 au.com.dius.pact.model.RequestResponsePact;
|
||||||
import org.junit.Rule;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.Test;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
@RunWith(SpringRunner.class)
|
import static org.assertj.core.api.Assertions.*;
|
||||||
@SpringBootTest(properties = {
|
|
||||||
// overriding provider address
|
@ExtendWith(PactConsumerTestExt.class)
|
||||||
"userservice.ribbon.listOfServers: localhost:8888"
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@PactTestFor(providerName = "userservice", port = "8888")
|
||||||
|
@SpringBootTest({
|
||||||
|
// overriding provider address
|
||||||
|
"userservice.ribbon.listOfServers: localhost:8888"
|
||||||
})
|
})
|
||||||
public class UserServiceConsumerTest {
|
class UserServiceConsumerTest {
|
||||||
|
|
||||||
@Rule
|
@Autowired
|
||||||
public PactProviderRuleMk2 stubProvider = new PactProviderRuleMk2("userservice", "localhost", 8888, this);
|
private UserClient userClient;
|
||||||
|
|
||||||
@Autowired
|
@Pact(state = "provider accepts a new person", provider = "userservice", consumer = "userclient")
|
||||||
private UserClient userClient;
|
RequestResponsePact createPersonPact(PactDslWithProvider builder) {
|
||||||
|
|
||||||
@Pact(state = "provider accepts a new person", provider = "userservice", consumer = "userclient")
|
// @formatter:off
|
||||||
public RequestResponsePact createPersonPact(PactDslWithProvider builder) {
|
return builder
|
||||||
return builder
|
.given("provider accepts a new person")
|
||||||
.given("provider accepts a new person")
|
.uponReceiving("a request to POST a person")
|
||||||
.uponReceiving("a request to POST a person")
|
.path("/user-service/users")
|
||||||
.path("/user-service/users")
|
.method("POST")
|
||||||
.method("POST")
|
.willRespondWith()
|
||||||
.willRespondWith()
|
.status(201)
|
||||||
.status(201)
|
.matchHeader("Content-Type", "application/json")
|
||||||
.matchHeader("Content-Type", "application/json")
|
.body(new PactDslJsonBody()
|
||||||
.body(new PactDslJsonBody()
|
.integerType("id", 42))
|
||||||
.integerType("id", 42))
|
.toPact();
|
||||||
.toPact();
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
@Pact(state = "person 42 exists", provider = "userservice", consumer = "userclient")
|
@Pact(state = "person 42 exists", provider = "userservice", consumer = "userclient")
|
||||||
public RequestResponsePact updatePersonPact(PactDslWithProvider builder) {
|
RequestResponsePact updatePersonPact(PactDslWithProvider builder) {
|
||||||
return builder
|
// @formatter:off
|
||||||
.given("person 42 exists")
|
return builder
|
||||||
.uponReceiving("a request to PUT a person")
|
.given("person 42 exists")
|
||||||
.path("/user-service/users/42")
|
.uponReceiving("a request to PUT a person")
|
||||||
.method("PUT")
|
.path("/user-service/users/42")
|
||||||
.willRespondWith()
|
.method("PUT")
|
||||||
.status(200)
|
.willRespondWith()
|
||||||
.matchHeader("Content-Type", "application/json")
|
.status(200)
|
||||||
.body(new PactDslJsonBody()
|
.matchHeader("Content-Type", "application/json")
|
||||||
.stringType("firstName", "Zaphod")
|
.body(new PactDslJsonBody()
|
||||||
.stringType("lastName", "Beeblebrox"))
|
.stringType("firstName", "Zaphod")
|
||||||
.toPact();
|
.stringType("lastName", "Beeblebrox"))
|
||||||
}
|
.toPact();
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@PactVerification(fragment = "createPersonPact")
|
@PactTestFor(pactMethod = "createPersonPact")
|
||||||
public void verifyCreatePersonPact() {
|
void verifyCreatePersonPact() {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setFirstName("Zaphod");
|
user.setFirstName("Zaphod");
|
||||||
user.setLastName("Beeblebrox");
|
user.setLastName("Beeblebrox");
|
||||||
IdObject id = userClient.createUser(user);
|
IdObject id = userClient.createUser(user);
|
||||||
assertThat(id.getId()).isEqualTo(42);
|
assertThat(id.getId()).isEqualTo(42);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@PactVerification(fragment = "updatePersonPact")
|
@PactTestFor(pactMethod = "updatePersonPact")
|
||||||
public void verifyUpdatePersonPact() {
|
void verifyUpdatePersonPact() {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setFirstName("Zaphod");
|
user.setFirstName("Zaphod");
|
||||||
user.setLastName("Beeblebrox");
|
user.setLastName("Beeblebrox");
|
||||||
User updatedUser = userClient.updateUser(42L, user);
|
User updatedUser = userClient.updateUser(42L, user);
|
||||||
assertThat(updatedUser.getFirstName()).isEqualTo("Zaphod");
|
assertThat(updatedUser.getFirstName()).isEqualTo("Zaphod");
|
||||||
assertThat(updatedUser.getLastName()).isEqualTo("Beeblebrox");
|
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-6.5-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-6.5-bin.zip
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user