├── .gitignore ├── Dockerfile ├── README.md ├── aws └── user_data.txt ├── build.gradle ├── build_image.sh ├── buildspec.yaml ├── cloudwatch-agent.json ├── docker-compose.yml ├── dropDynamoLocal.sh ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── populateDynamoLocal.sh ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── codigo_morsa │ │ └── docker │ │ ├── DockerApplication.java │ │ ├── configuration │ │ ├── AudienceValidator.java │ │ ├── ClaimAdapter.java │ │ ├── DatabaseConfig.java │ │ ├── DynamoDbConfig.java │ │ ├── MeterConfig.java │ │ └── Security.java │ │ ├── controllers │ │ ├── Controller.java │ │ ├── ControllerExceptionHandler.java │ │ ├── HttpErrorInfo.java │ │ └── LenguajeController.java │ │ ├── model │ │ ├── Customer.java │ │ └── Lenguaje.java │ │ ├── repository │ │ ├── CustomerRepository.java │ │ └── LenguajeRepository.java │ │ └── services │ │ └── CustomerService.java └── resources │ ├── application-docker.yml │ ├── application.yml │ ├── logback-spring.xml │ └── schema.sql └── test └── java └── com └── codigo_morsa └── docker └── DockerApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | logs/ 39 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM 371417955885.dkr.ecr.us-east-1.amazonaws.com/11.0.4-jre-slim:v1 2 | RUN adduser --system --group spring 3 | USER spring:spring 4 | ARG DEPENDENCY=build/dependency 5 | COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib 6 | COPY ${DEPENDENCY}/META-INF /app/META-INF 7 | COPY ${DEPENDENCY}/BOOT-INF/classes /app 8 | ENTRYPOINT ["java","-cp","app:app/lib/*","-Dspring.profiles.active=docker","com.codigo_morsa.docker.DockerApplication"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Spring Cloud 2 | 3 | Just testing out some features of the https://awspring.io/ 4 | 5 | - Parameter Store 6 | 7 | And also trying [micrometer](https://micrometer.io/) for pushing metrics into Cloudwatch. 8 | 9 | ## How to unpack the JAR for containerization 10 | 11 | First, build the project and generate the fat JAR in build/libs (just run gradle). 12 | 13 | Then execute: 14 | ````bash 15 | mkdir build/dependency 16 | cd build/dependency 17 | jar -xf ../libs/*.jar 18 | ```` 19 | 20 | This will separate the fat JAR into BOOT-INF and META-INF. 21 | In order to build the docker image: 22 | 23 | ````bash 24 | docker build -t . 25 | ```` 26 | 27 | The previous steps can be done automatically just executing 28 | ````bash 29 | mkdir build/dependency 30 | sh build_image.sh 31 | ```` 32 | 33 | Beware! This assumes you have an ECR repository with a public image named __public.ecr.aws/f5n7q8r5/aws-spring-param-store__. 34 | Feel free to push the image to another repo like Docker Hub and to name it however you want. 35 | 36 | ## Testing the image in a EC2 instance 37 | 38 | A plain EC2 instance with Amazon Linux 2 and docker installed is enough to test this. 39 | 40 | - Approach A (Recommended) 41 | - Create an AMI out of Amazon Linux 2 with docker 42 | - Launch an EC2 instance from this custom AMI 43 | - Assign appropiate IAM Role for SSM Param store, Cloudwatch put-metric/put-logs/... and eventually DynamoDB read/write. 44 | - Use aws/user_data.txt as the boot script when launching an EC2 instance 45 | - Approach B 46 | - Launch an EC2 instance from default Amazon Linux 2 47 | - Assign appropiate IAM Role (permissions above) 48 | - Connect to it via ssh or AWS Console 49 | - Install docker manually 50 | - Execute 1 by 1 each command at aws/user_data.txt 51 | 52 | To install docker manually [in the EC2](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html). 53 | 54 | ### Instance Profile Authentication 55 | 56 | As we are not providing the credentials neither in the environment nor as java system properties, the [AWS Credentials Provider Chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html) is going to keep 57 | looking for credentials in other levels. In this example we are using the Instance profile credentials, which under my perspective 58 | are the safest ones. 59 | 60 | __Do not forget__ to assign the appropiate IAM role to the EC2 for reading from the Parameter Store and for pushing metrics into CloudWatch. 61 | For this basic example the __AmazonSSMReadOnlyAccess__ plus the __CloudWatchAgentServerPolicy__ roles are going to be enough, although you will want 62 | to apply in real life apps the _least privilege permissions_ principle, in case the former policies are too permisive. -------------------------------------------------------------------------------- /aws/user_data.txt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | service docker start 3 | docker pull public.ecr.aws/f5n7q8r5/aws-spring-param-store 4 | mkdir ~/app_logs 5 | chmod -R 777 ~/app_logs 6 | docker run -p 80:8080 --name testB -d \ 7 | -v ~/app_logs:/app_logs \ 8 | --env serverId=$(hostname -f) \ 9 | public.ecr.aws/f5n7q8r5/aws-spring-param-store -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.5.10' 3 | id 'io.spring.dependency-management' version '1.0.11.RELEASE' 4 | id 'java' 5 | } 6 | 7 | group = 'com.codigo_morsa' 8 | version = '0.0.1-SNAPSHOT' 9 | sourceCompatibility = '11' 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc' 17 | implementation 'org.springframework.boot:spring-boot-starter-webflux' 18 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 19 | implementation 'org.springframework.boot:spring-boot-starter-security' 20 | implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.4.0' 21 | implementation 'io.awspring.cloud:spring-cloud-starter-aws-parameter-store-config:2.4.0' 22 | implementation 'io.micrometer:micrometer-core' 23 | implementation 'io.micrometer:micrometer-registry-cloudwatch2' 24 | implementation 'org.springframework.security:spring-security-oauth2-resource-server' 25 | implementation 'org.springframework.security:spring-security-oauth2-jose' 26 | 27 | implementation 'software.amazon.awssdk:dynamodb-enhanced:2.15.82' 28 | 29 | runtimeOnly 'com.h2database:h2' 30 | runtimeOnly 'io.r2dbc:r2dbc-h2' 31 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 32 | testImplementation 'io.projectreactor:reactor-test' 33 | } 34 | 35 | dependencyManagement { 36 | imports { 37 | mavenBom "software.amazon.awssdk:bom:2.15.82" 38 | } 39 | } 40 | 41 | tasks.named('test') { 42 | useJUnitPlatform() 43 | } 44 | 45 | bootJar { 46 | manifest { 47 | attributes 'Start-Class': 'com.codigo_morsa.docker.DockerApplication' 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /build_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | abort() 4 | { 5 | echo >&2 ' 6 | *************** 7 | *** ABORTED *** 8 | *************** 9 | ' 10 | echo "An error occurred. Exiting..." >&2 11 | exit 1 12 | } 13 | 14 | trap 'abort' 0 15 | 16 | set -e 17 | 18 | sh ./gradlew bootJar 19 | mkdir build/dependency 20 | cd build/dependency 21 | jar -xf ../libs/*.jar 22 | cd ../.. 23 | 24 | aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 371417955885.dkr.ecr.us-east-1.amazonaws.com 25 | 26 | docker build -t springio/dockerize-spring-boot . 27 | 28 | # Extract branch name from CODEBUILD_WEBHOOK_HEAD_REF 29 | # E.g. refs/heads/main 30 | regex="([^\/]+$)" 31 | echo $CODEBUILD_WEBHOOK_EVENT 32 | echo $CODEBUILD_WEBHOOK_BASE_REF 33 | echo $CODEBUILD_WEBHOOK_HEAD_REF 34 | TAG=$([ "$CODEBUILD_WEBHOOK_EVENT" = "PULL_REQUEST_MERGED" ] && [ "$CODEBUILD_WEBHOOK_BASE_REF" = "refs/heads/main" ] && echo "latest" || if [[ $CODEBUILD_WEBHOOK_HEAD_REF =~ $regex ]]; then echo ${BASH_REMATCH[1]}; fi) 35 | IMAGE_URI=public.ecr.aws/f5n7q8r5/aws-spring-param-store:$TAG 36 | echo "the tag $TAG, $IMAGE_URI" 37 | docker tag springio/dockerize-spring-boot:latest $IMAGE_URI 38 | 39 | aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/f5n7q8r5 40 | 41 | docker push $IMAGE_URI 42 | 43 | trap : 0 44 | 45 | echo >&2 ' 46 | ************ 47 | *** DONE *** 48 | ************ 49 | ' -------------------------------------------------------------------------------- /buildspec.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | runtime-versions: 6 | java: corretto11 7 | build: 8 | commands: 9 | - echo Build started on `date` 10 | - java --version 11 | - bash build_image.sh 12 | post_build: 13 | commands: 14 | - echo Build completed on `date` 15 | -------------------------------------------------------------------------------- /cloudwatch-agent.json: -------------------------------------------------------------------------------- 1 | { 2 | "logs": { 3 | "logs_collected": { 4 | "files": { 5 | "collect_list": [ 6 | { 7 | "file_path": "/home/ec2-user/app_logs/*-docker.log", 8 | "log_group_name": "spring-aws-prod-logging", 9 | "log_stream_name": "spring-aws-prod-logging-{instance_id}.log", 10 | "timestamp_format": "%Y-%m-%d %H:%M:%S.%f", 11 | "multi_line_start_pattern": "{timestamp_format}" 12 | }, 13 | { 14 | "file_path": "/home/ec2-user/app_logs/*-dev.log", 15 | "log_group_name": "spring-aws-dev-logging", 16 | "log_stream_name": "spring-aws-dev-logging-{instance_id}.log", 17 | "timestamp_format": "%Y-%m-%d %H:%M:%S.%f", 18 | "multi_line_start_pattern": "{timestamp_format}" 19 | } 20 | ] 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.2' 2 | 3 | services: 4 | localstack: 5 | image: localstack/localstack 6 | hostname: localstack 7 | expose: 8 | - 4566 9 | ports: 10 | - "4566:4566" 11 | environment: 12 | - SERVICES=dynamodb 13 | - DEFAULT_REGION=us-east-1 14 | - HOSTNAME_EXTERNAL=localstack 15 | volumes: 16 | - "${TMPDIR:-/tmp/localstack}:/tmp/localstack" 17 | - "/var/run/docker.sock:/var/run/docker.sock" -------------------------------------------------------------------------------- /dropDynamoLocal.sh: -------------------------------------------------------------------------------- 1 | aws dynamodb delete-table \ 2 | --table-name lenguaje \ 3 | --profile default \ 4 | --endpoint-url http://localhost:4566 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinKindall/aws_spring_cloud_sandbox/a4b33c2f21bcefe965f4194f777123b7d9014292/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /populateDynamoLocal.sh: -------------------------------------------------------------------------------- 1 | aws dynamodb create-table \ 2 | --table-name lenguaje \ 3 | --attribute-definitions \ 4 | AttributeName=name,AttributeType=S \ 5 | --key-schema AttributeName=name,KeyType=HASH \ 6 | --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \ 7 | --profile default \ 8 | --endpoint-url http://localhost:4566 -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'docker' 2 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/DockerApplication.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class DockerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DockerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/configuration/AudienceValidator.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.configuration; 2 | 3 | import org.springframework.security.oauth2.core.OAuth2Error; 4 | import org.springframework.security.oauth2.core.OAuth2TokenValidator; 5 | import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; 6 | import org.springframework.security.oauth2.jwt.Jwt; 7 | 8 | class AudienceValidator implements OAuth2TokenValidator { 9 | private final String audience; 10 | 11 | AudienceValidator(String audience) { 12 | this.audience = audience; 13 | } 14 | 15 | @Override 16 | public OAuth2TokenValidatorResult validate(Jwt jwt) { 17 | OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is either missing or wrong", null); 18 | 19 | if (jwt.getAudience().contains(audience)) { 20 | return OAuth2TokenValidatorResult.success(); 21 | } 22 | return OAuth2TokenValidatorResult.failure(error); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/configuration/ClaimAdapter.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.configuration; 2 | 3 | import org.springframework.core.convert.converter.Converter; 4 | import org.springframework.security.oauth2.jwt.MappedJwtClaimSetConverter; 5 | 6 | import java.util.Collections; 7 | import java.util.Map; 8 | 9 | public class ClaimAdapter implements 10 | Converter, Map> { 11 | 12 | private final MappedJwtClaimSetConverter delegate = 13 | MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap()); 14 | 15 | @Override 16 | public Map convert(Map claims) { 17 | Map convertedClaims = this.delegate.convert(claims); 18 | 19 | Object adminClaim = convertedClaims.get("custom:admin"); 20 | if (adminClaim != null && adminClaim.equals("true")) { 21 | convertedClaims.put("scope", "admin"); 22 | } 23 | 24 | return convertedClaims; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/configuration/DatabaseConfig.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.configuration; 2 | 3 | import com.codigo_morsa.docker.model.Customer; 4 | import com.codigo_morsa.docker.repository.CustomerRepository; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import java.util.Arrays; 10 | 11 | @Configuration 12 | public class DatabaseConfig { 13 | 14 | @Bean 15 | public CommandLineRunner demo (CustomerRepository repository) { 16 | return (args) -> { 17 | repository.saveAll(Arrays.asList( 18 | new Customer("Codigo", "Morsa"), 19 | new Customer("Ada", "Lovelace"), 20 | new Customer("Steve", "Woz") 21 | )).then().block(); 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/configuration/DynamoDbConfig.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.configuration; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient; 7 | import software.amazon.awssdk.regions.Region; 8 | import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; 9 | 10 | import java.net.URI; 11 | 12 | @Configuration 13 | public class DynamoDbConfig { 14 | 15 | @Value("${dynamoUrl}") 16 | private String dynamoDbUrl; 17 | 18 | @Bean 19 | public DynamoDbEnhancedAsyncClient getDynamoDbEnhancedAsyncClient() { 20 | return DynamoDbEnhancedAsyncClient.builder() 21 | .dynamoDbClient(getDynamoDbAsyncClient()) 22 | .build(); 23 | } 24 | 25 | private DynamoDbAsyncClient getDynamoDbAsyncClient() { 26 | return DynamoDbAsyncClient 27 | .builder() 28 | .region(Region.US_EAST_1) 29 | .endpointOverride(URI.create(dynamoDbUrl)) 30 | .build(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/configuration/MeterConfig.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.configuration; 2 | 3 | import io.micrometer.cloudwatch2.CloudWatchConfig; 4 | import io.micrometer.cloudwatch2.CloudWatchMeterRegistry; 5 | import io.micrometer.core.instrument.Clock; 6 | import io.micrometer.core.instrument.MeterRegistry; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import software.amazon.awssdk.regions.Region; 10 | import software.amazon.awssdk.services.cloudwatch.CloudWatchAsyncClient; 11 | 12 | import java.time.Duration; 13 | import java.util.Map; 14 | 15 | @Configuration 16 | public class MeterConfig { 17 | 18 | @Bean 19 | public MeterRegistry getMeterRegistry() { 20 | var cloudWatchConfig = setupCloudWatchConfig(); 21 | 22 | return new CloudWatchMeterRegistry( 23 | cloudWatchConfig, 24 | Clock.SYSTEM, 25 | cloudWatchAsyncClient() 26 | ); 27 | } 28 | 29 | private CloudWatchAsyncClient cloudWatchAsyncClient() { 30 | return CloudWatchAsyncClient 31 | .builder() 32 | .region(Region.US_EAST_1) 33 | .build(); 34 | } 35 | 36 | private CloudWatchConfig setupCloudWatchConfig() { 37 | return new CloudWatchConfig() { 38 | private final Map configuration = Map.of( 39 | "cloudwatch.namespace", "sandboxApp", 40 | "cloudwatch.step", Duration.ofMinutes(1).toString() 41 | ); 42 | 43 | @Override 44 | public String get(String key) { 45 | return configuration.get(key); 46 | } 47 | }; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/configuration/Security.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.configuration; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 6 | import org.springframework.security.config.web.server.ServerHttpSecurity; 7 | import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; 8 | import org.springframework.security.oauth2.core.OAuth2TokenValidator; 9 | import org.springframework.security.oauth2.jwt.*; 10 | import org.springframework.security.web.server.SecurityWebFilterChain; 11 | 12 | import static org.springframework.http.HttpMethod.GET; 13 | import static org.springframework.http.HttpMethod.OPTIONS; 14 | 15 | @EnableWebFluxSecurity 16 | public class Security { 17 | @Value("${spring.security.oauth2.client.provider.cognito.issuerUri}") 18 | private String issuer; 19 | 20 | @Value("${spring.security.oauth2.client.provider.cognito.audience}") 21 | private String audience; 22 | 23 | @Bean 24 | SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { 25 | http 26 | .authorizeExchange() 27 | .pathMatchers("/actuator/**").permitAll() 28 | .pathMatchers(OPTIONS, "/**").permitAll() 29 | .pathMatchers(GET, "/config").hasAuthority("SCOPE_admin") 30 | .anyExchange().authenticated() 31 | .and() 32 | .cors() 33 | .and() 34 | .oauth2ResourceServer() 35 | .jwt() 36 | .jwtDecoder(jwtDecoder()); 37 | 38 | return http.build(); 39 | } 40 | 41 | ReactiveJwtDecoder jwtDecoder() { 42 | NimbusReactiveJwtDecoder jwtDecoder = (NimbusReactiveJwtDecoder) 43 | ReactiveJwtDecoders.fromOidcIssuerLocation(issuer); 44 | 45 | OAuth2TokenValidator audienceValidator = new AudienceValidator(audience); 46 | OAuth2TokenValidator withIssuer = JwtValidators.createDefaultWithIssuer(issuer); 47 | OAuth2TokenValidator withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator); 48 | 49 | jwtDecoder.setJwtValidator(withAudience); 50 | jwtDecoder.setClaimSetConverter(new ClaimAdapter()); 51 | 52 | return jwtDecoder; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/controllers/Controller.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.controllers; 2 | 3 | import com.codigo_morsa.docker.model.Customer; 4 | import com.codigo_morsa.docker.services.CustomerService; 5 | import io.micrometer.core.instrument.Counter; 6 | import io.micrometer.core.instrument.MeterRegistry; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RestController; 13 | import reactor.core.publisher.Flux; 14 | import reactor.core.publisher.Mono; 15 | 16 | import java.util.Map; 17 | 18 | @RestController 19 | public class Controller { 20 | private final Counter customerViewCounter; 21 | 22 | @Value("${message}") 23 | private String message; 24 | 25 | @Value("${serverId}") 26 | private String serverId; 27 | 28 | private final CustomerService customerService; 29 | 30 | @Autowired 31 | public Controller(CustomerService customerService, MeterRegistry meterRegistry) { 32 | this.customerService = customerService; 33 | customerViewCounter = meterRegistry.counter("CUSTOMER.reads"); 34 | } 35 | 36 | @GetMapping("/customer") 37 | public Flux getCustomers() { 38 | customerViewCounter.increment(); 39 | return customerService.getCustomers(); 40 | } 41 | 42 | @PostMapping("/customer") 43 | public Mono> saveCustomer( 44 | @RequestBody Customer customer 45 | ) { 46 | return customerService 47 | .saveCustomer(customer) 48 | .map(id -> Map.of("message", "Customer created with id: " + id)); 49 | } 50 | 51 | @GetMapping("/config") 52 | public Mono> getConfigMessage() { 53 | return Mono.just(Map.of("message", "Here comes a message from AWS Param Store: " + message)); 54 | } 55 | 56 | @GetMapping("/serverId") 57 | public Mono> getServerId() { 58 | return Mono.just(Map.of("message", "You are running on the server: " + serverId)); 59 | } 60 | 61 | @GetMapping("/launchException") 62 | public Mono> triggerException() { 63 | throw new RuntimeException("Oops, something unexpected happened."); 64 | } 65 | 66 | @GetMapping("/launchException2") 67 | public Mono> triggerException2() { 68 | throw new ArithmeticException("Cannot divide by 0"); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/controllers/ControllerExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.controllers; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.server.reactive.ServerHttpRequest; 6 | import org.springframework.web.bind.annotation.ExceptionHandler; 7 | import org.springframework.web.bind.annotation.ResponseBody; 8 | import org.springframework.web.bind.annotation.ResponseStatus; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; 11 | 12 | import static org.springframework.http.HttpStatus.CONFLICT; 13 | import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; 14 | 15 | @RestControllerAdvice 16 | public class ControllerExceptionHandler { 17 | 18 | Logger logger = LoggerFactory.getLogger(ControllerExceptionHandler.class); 19 | 20 | @ResponseStatus(CONFLICT) 21 | @ExceptionHandler(ConditionalCheckFailedException.class) 22 | public @ResponseBody 23 | HttpErrorInfo handleNotFoundExceptions(ServerHttpRequest request, Exception ex) { 24 | logger.info("ConditionalCheckFailedException: {}", ex.getMessage()); 25 | return new HttpErrorInfo(CONFLICT, request.getPath().pathWithinApplication().value(), "Lenguaje ya existe!"); 26 | } 27 | 28 | // default handler 29 | @ResponseStatus(INTERNAL_SERVER_ERROR) 30 | @ExceptionHandler(Exception.class) 31 | public @ResponseBody 32 | HttpErrorInfo handleUnknownExceptions(ServerHttpRequest request, Exception ex) { 33 | logger.error("Unknown Exception: {}", ex.getMessage()); 34 | return new HttpErrorInfo(INTERNAL_SERVER_ERROR, request.getPath().pathWithinApplication().value(), "Server error"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/controllers/HttpErrorInfo.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.controllers; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | import java.time.ZonedDateTime; 6 | 7 | public class HttpErrorInfo { 8 | private final ZonedDateTime timestamp; 9 | private final String path; 10 | private final HttpStatus httpStatus; 11 | private final String message; 12 | 13 | 14 | public HttpErrorInfo() { 15 | timestamp = null; 16 | this.httpStatus = null; 17 | this.path = null; 18 | this.message = null; 19 | } 20 | 21 | public HttpErrorInfo(HttpStatus httpStatus, String path, String message) { 22 | timestamp = ZonedDateTime.now(); 23 | this.httpStatus = httpStatus; 24 | this.path = path; 25 | this.message = message; 26 | } 27 | 28 | public ZonedDateTime getTimestamp() { 29 | return timestamp; 30 | } 31 | 32 | public String getPath() { 33 | return path; 34 | } 35 | 36 | public int getStatus() { 37 | return httpStatus.value(); 38 | } 39 | 40 | public String getError() { 41 | return httpStatus.getReasonPhrase(); 42 | } 43 | 44 | public String getMessage() { 45 | return message; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/controllers/LenguajeController.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.controllers; 2 | 3 | import com.codigo_morsa.docker.model.Lenguaje; 4 | import com.codigo_morsa.docker.repository.LenguajeRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.*; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | import java.util.Map; 11 | 12 | @RestController 13 | public class LenguajeController { 14 | 15 | private final LenguajeRepository repository; 16 | 17 | @Autowired 18 | public LenguajeController(LenguajeRepository repository) { 19 | this.repository = repository; 20 | } 21 | 22 | @GetMapping("/lenguaje") 23 | public Flux getAllLenguajes() { 24 | return repository.findAll(); 25 | } 26 | 27 | @GetMapping("/lenguaje/{name}") 28 | public Mono getAllLenguajes(@PathVariable String name) { 29 | return repository.getLenguajeByName(name); 30 | } 31 | 32 | @PostMapping("/lenguaje") 33 | public Mono> saveLenguaje(@RequestBody Lenguaje lenguaje) { 34 | return repository.saveLenguaje(lenguaje).thenReturn(Map.of("message", "Lenguaje creado!")); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/model/Customer.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.model; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class Customer { 6 | 7 | @Id 8 | public Long id; 9 | 10 | private final String firstName; 11 | private final String lastName; 12 | 13 | public Customer(String firstName, String lastName) { 14 | this.firstName = firstName; 15 | this.lastName = lastName; 16 | } 17 | 18 | public String getFirstName() { 19 | return firstName; 20 | } 21 | 22 | public String getLastName() { 23 | return lastName; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/model/Lenguaje.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.model; 2 | 3 | import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; 4 | import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; 5 | import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; 6 | 7 | @DynamoDbBean 8 | public class Lenguaje { 9 | 10 | private String name; 11 | private long publishedAt; 12 | private boolean tipadoEstatico; 13 | 14 | @DynamoDbPartitionKey 15 | @DynamoDbAttribute("name") 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | @DynamoDbAttribute("published_at") 21 | public long getPublishedAt() { 22 | return publishedAt; 23 | } 24 | 25 | @DynamoDbAttribute("tipado_estatico") 26 | public boolean getTipadoEstatico() { 27 | return tipadoEstatico; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | public void setPublishedAt(long publishedAt) { 35 | this.publishedAt = publishedAt; 36 | } 37 | 38 | public void setTipadoEstatico(boolean tipadoEstatico) { 39 | this.tipadoEstatico = tipadoEstatico; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.repository; 2 | 3 | import com.codigo_morsa.docker.model.Customer; 4 | import org.springframework.data.r2dbc.repository.Query; 5 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 6 | import reactor.core.publisher.Flux; 7 | 8 | public interface CustomerRepository extends ReactiveCrudRepository { 9 | 10 | @Query("SELECT * FROM customer WHERE last_name = :lastname") 11 | Flux findByLastName(String lastName); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/repository/LenguajeRepository.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.repository; 2 | 3 | import com.codigo_morsa.docker.model.Lenguaje; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Repository; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | import software.amazon.awssdk.enhanced.dynamodb.*; 9 | import software.amazon.awssdk.enhanced.dynamodb.model.PutItemEnhancedRequest; 10 | 11 | import java.util.Map; 12 | 13 | @Repository 14 | public class LenguajeRepository { 15 | 16 | private final DynamoDbAsyncTable lenguajeTable; 17 | 18 | @Autowired 19 | public LenguajeRepository(DynamoDbEnhancedAsyncClient dynamoDbClient) { 20 | lenguajeTable = dynamoDbClient 21 | .table("lenguaje", TableSchema.fromBean(Lenguaje.class)); 22 | } 23 | 24 | public Flux findAll() { 25 | return Flux.from(lenguajeTable.scan().items()); 26 | } 27 | 28 | public Mono getLenguajeByName(String name) { 29 | return Mono.fromFuture(lenguajeTable.getItem(getKeyBuild(name))); 30 | } 31 | 32 | public Mono saveLenguaje(Lenguaje lenguaje) { 33 | var request = PutItemEnhancedRequest 34 | .builder(Lenguaje.class) 35 | .item(lenguaje) 36 | .conditionExpression( 37 | Expression.builder() 38 | .expression("attribute_not_exists(#name)") 39 | .expressionNames(Map.of("#name", "name")) 40 | .build()) 41 | .build(); 42 | return Mono.fromFuture(lenguajeTable.putItem(request)); 43 | } 44 | 45 | private Key getKeyBuild(String name) { 46 | return Key.builder().partitionValue(name).build(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/codigo_morsa/docker/services/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker.services; 2 | 3 | import com.codigo_morsa.docker.model.Customer; 4 | import com.codigo_morsa.docker.repository.CustomerRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | @Service 11 | public class CustomerService { 12 | 13 | private final CustomerRepository repository; 14 | 15 | @Autowired 16 | public CustomerService(CustomerRepository repository) { 17 | this.repository = repository; 18 | } 19 | 20 | public Flux getCustomers() { 21 | return repository.findAll(); 22 | } 23 | 24 | public Mono saveCustomer(Customer customer) { 25 | return repository.save(customer).map(cust -> cust.id); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/application-docker.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | config: 3 | import: "aws-parameterstore:" 4 | 5 | cloud: 6 | aws: 7 | credentials: 8 | instance-profile: true 9 | region: 10 | static: us-east-1 11 | stack: 12 | auto: true 13 | 14 | dynamoUrl: "https://dynamodb.us-east-1.amazonaws.com" 15 | 16 | logging: 17 | level: 18 | com: 19 | codigo_morsa: INFO 20 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: myapplication 4 | security: 5 | oauth2: 6 | client: 7 | provider: 8 | cognito: 9 | audience: 5v8hql2lrvd8ob25j27j0vmbah 10 | issuerUri: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_vLvD5n28f 11 | 12 | cloud: 13 | aws: 14 | stack: 15 | auto: false 16 | 17 | message: justAnyLocalMachine 18 | 19 | dynamoUrl: "http://localhost:4566" 20 | 21 | serverId: "local" 22 | 23 | logging: 24 | level: 25 | com: 26 | codigo_morsa: DEBUG -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | %black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{1.}): %msg%n%throwable 28 | 29 | 30 | 31 | 33 | ${FILE_PATH} 34 | 35 | %d %p %C{1.} [%t] %m%n 36 | 37 | 38 | 39 | ${ROLLING_POLICY_LOG_FILE_PATTERN} 40 | ${MAX_FILE_SIZE} 41 | 42 | 47 | ${TOTAL_SIZE_CAP} 48 | 49 | 56 | ${MAX_HISTORY} 57 | 58 | 64 | true 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE customer (id SERIAL PRIMARY KEY, first_name VARCHAR(255), last_name VARCHAR(255)); -------------------------------------------------------------------------------- /src/test/java/com/codigo_morsa/docker/DockerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.codigo_morsa.docker; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class DockerApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------