├── .github └── workflows │ └── build.yml ├── README.md ├── hello-service ├── .gitignore ├── Dockerfile ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── java │ └── it │ │ └── valeriovaudi │ │ └── helloservice │ │ └── HelloServiceApplication.java │ └── resources │ └── bootstrap.yml ├── helm └── spring-cloud-kubernetes-demo │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── NOTES.txt │ ├── _hello-service-helpers.tpl │ ├── _message-service-helpers.tpl │ ├── _ui-interface-helpers.tpl │ ├── hello-service.yml │ ├── message-service.yml │ ├── serviceaccount.yaml │ └── ui-interface.yml │ └── values.yaml ├── images ├── messages_webapp.png └── user_webapp.png ├── message-service ├── .gitignore ├── Dockerfile ├── build.gradle.kts ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ └── main │ ├── kotlin │ └── it │ │ └── valeriovaudi │ │ └── messageservice │ │ └── MessageServiceApplication.kt │ └── resources │ └── bootstrap.yml └── ui ├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── Dockerfile ├── mvnw ├── mvnw.cmd ├── pom.xml └── src └── main ├── frontend ├── app │ ├── component │ │ ├── Container.js │ │ ├── Jumbotron.js │ │ ├── NavBar.js │ │ └── TextInputForm.js │ ├── messages-site │ │ ├── MessageSiteApp.js │ │ └── index.js │ ├── repository │ │ └── MessageRepository.js │ └── site │ │ ├── MainSiteApp.js │ │ └── index.js ├── package.json └── webpack.config.js ├── java └── it │ └── valeriovaudi │ └── ui │ └── UiApplication.java └── resources ├── bootstrap.yml └── static ├── index.html └── messages.html /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: { } 5 | workflow_dispatch: { } 6 | 7 | jobs: 8 | build-ui: 9 | runs-on: ubuntu-20.04 10 | 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - name: Set up JDK 17 15 | uses: actions/setup-java@v3 16 | with: 17 | java-version: 17 18 | distribution: 'corretto' 19 | 20 | - name: Build 21 | run: | 22 | cd ui 23 | mvn clean install -q 24 | 25 | - name: docker push 26 | run: | 27 | cd ui 28 | if [[ ${GITHUB_REF##*/} == 'master' ]] 29 | then 30 | DOCKER_TAG="latest" 31 | else 32 | DOCKER_TAG="${GITHUB_REF##*/}" 33 | fi 34 | echo "$DOCKER_TAG" 35 | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }} 36 | docker build . -t mrflick72/ui-interface:$DOCKER_TAG 37 | docker push mrflick72/ui-interface:$DOCKER_TAG 38 | 39 | build-message-service: 40 | runs-on: ubuntu-20.04 41 | 42 | steps: 43 | - uses: actions/checkout@v3 44 | 45 | - name: Set up JDK 17 46 | uses: actions/setup-java@v3 47 | with: 48 | java-version: 17 49 | distribution: 'corretto' 50 | 51 | - name: Build 52 | run: | 53 | cd message-service 54 | ./gradlew build 55 | 56 | - name: docker push 57 | run: | 58 | cd message-service 59 | if [[ ${GITHUB_REF##*/} == 'master' ]] 60 | then 61 | DOCKER_TAG="latest" 62 | else 63 | DOCKER_TAG="${GITHUB_REF##*/}" 64 | fi 65 | echo "$DOCKER_TAG" 66 | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }} 67 | docker build . -t mrflick72/message-service:$DOCKER_TAG 68 | docker push mrflick72/message-service:$DOCKER_TAG 69 | 70 | build-hello-service: 71 | runs-on: ubuntu-20.04 72 | 73 | steps: 74 | - uses: actions/checkout@v3 75 | 76 | - name: Set up JDK 17 77 | uses: actions/setup-java@v3 78 | with: 79 | java-version: 17 80 | distribution: 'corretto' 81 | 82 | - name: Build 83 | run: | 84 | cd hello-service 85 | ./gradlew build 86 | 87 | - name: docker push 88 | run: | 89 | cd hello-service 90 | if [[ ${GITHUB_REF##*/} == 'master' ]] 91 | then 92 | DOCKER_TAG="latest" 93 | else 94 | DOCKER_TAG="${GITHUB_REF##*/}" 95 | fi 96 | echo "$DOCKER_TAG" 97 | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }} 98 | docker build . -t mrflick72/hello-service:$DOCKER_TAG 99 | docker push mrflick72/hello-service:$DOCKER_TAG -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## The use case 2 | 3 | The sample application is a very simple hello world web application. This application is thought for explore service composition, 4 | client load balancing and configuration management. The web main application is composed by two pages under security: 5 | * a web page for standard users in order to get a special hello with a random special message for you on the root or /index.html web application path 6 | * a web page for admin user in order to manage the special message list on for /messages.html web application path. 7 | * there exist a prebuilt user for both web application 8 | * username **user** and password **secret** for /index.html web application 9 | * username **admin** and password **secret** for /messages.html web application 10 | 11 | ### Web Application for simple user screenshot 12 | ![](https://raw.githubusercontent.com/mrFlick72/spring-cloud-kubernetes-demo/master/images/user_webapp.png) 13 | 14 | 15 | ### Web Application for admin user screenshot 16 | ![](https://raw.githubusercontent.com/mrFlick72/spring-cloud-kubernetes-demo/master/images/messages_webapp.png) 17 | 18 | ## The technology stack 19 | 20 | In this project you can see used many technologies like: 21 | 22 | * Spring Cloud Gateway: the reactive Spring counterpart of Zuul, build on Spring 5.x and Webflux 23 | * Spring Cloud Kubernetes 24 | * Spring Cloud LoadBalancer 25 | * Spring Reactive Data Mongo 26 | * Spring WebFlux 27 | * Spring Boot 3.x 28 | * Spring Session 29 | * Spring Reactive Security 30 | * Java/Kotlin 31 | 32 | 33 | ## How it works 34 | 35 | Basically on the UI thankful to Spring Cloud Gateway, similarity to Zuul, we can configure a micro proxy between ui-interface and the backend services. All the magic 36 | is performed by the yml configuration. Even if, in any talk on Spring Cloud Gateway probably you can see the java config way, in this example I preferred yml config in order to 37 | can benefit of hot reload route. The configured routes will be useful to get the hello message on the main web application and create or delete special messages on the 38 | messages web application accessed by admin users. the snippet of code that implements this magic is like below: 39 | 40 | ```yaml 41 | spring: 42 | cloud: 43 | gateway: 44 | routes: 45 | - id: hello-service 46 | uri: lb://hello-service/ 47 | predicates: 48 | - Path=/hello-service/** 49 | filters: 50 | - StripPrefix=1 51 | 52 | - id: message-service 53 | uri: lb://message-service/ 54 | predicates: 55 | - Path=/message-service/** 56 | filters: 57 | - StripPrefix=1 58 | ``` 59 | 60 | On hello-service, instead the integration with message-service is performed by a classic rest service call using a WebClient.Builder annotated with @LoadBalanced in order to 61 | benefit of the *LoadBalancerExchangeFilterFunction* injected by spring for us. The configuration is very simple and the usage is a classical rest service call via WebClient: 62 | 63 | #### load balancer confguration 64 | ```java 65 | @SpringBootApplication 66 | public class HelloServiceApplication { 67 | 68 | ... 69 | 70 | @Bean 71 | @LoadBalanced 72 | public WebClient.Builder loadBalancedWebClientBuilder() { 73 | return WebClient.builder(); 74 | } 75 | 76 | } 77 | ``` 78 | #### service integration 79 | 80 | ```java 81 | 82 | @Slf4j 83 | @Service 84 | class HelloService { 85 | 86 | ... 87 | 88 | Mono sayHello(String name) { 89 | return webClientBuilder.get() 90 | .uri(helloServiceUri) 91 | .retrieve() 92 | .bodyToMono(HashMap.class) 93 | .flatMap(payload -> just(format(TEMPLATE, name, INSTANCE_ID, payload.getOrDefault("message", DEFAULT_MESSAGE)))); 94 | } 95 | } 96 | ``` 97 | 98 | The application.yml configuration provided via config map. The benefit of use configmap 99 | with Spring Cloud Kubernetes is that configuring restart actuator endpoint with spring cloud kubernetes configuration in the application.yml, we can benefit of a hot reload configuration mechanism 100 | via Spring application context restart. 101 | 102 | The application is totally reactive and no blocking io. It involved: 103 | 104 | * Spring Cloud Gateway instead of Zuul 105 | * Spring WebFlux instead of a classical Spring MVC 106 | * Spring Data Reactive Mongo instead of Spring Data Mongo 107 | * WebClient instead of a plain RestTemplate 108 | 109 | Another point of attention may be the usage of Spring Session on Redis for in order to achieve a totally scalable application, without sticky session or something like that. 110 | 111 | ## How build the project 112 | In this spike I will use minikube like k8s local environment. In order to speed up a fresh minikube instance for this purpose 113 | you can use this command: 114 | ```minikube start --vm-driver=virtualbox --cpus 4 --memory 8192 -p spring-cloud-k8s``` 115 | 116 | Remember to enable ingress with this command: ```minikube addons enable ingress -p spring-cloud-k8s``` 117 | In order to test on minikube you can use my docker images on docker hub and that's it install the kubernetes manifests via helm chart under helm folder. 118 | 119 | if you want you can try it online ;) [user page](https://spring-cloud-kubernetes-demo.vvaudi-lab.com/index.html), or [admin page](https://spring-cloud-kubernetes-demo.vvaudi-lab.com/messages.html) 120 | -------------------------------------------------------------------------------- /hello-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | /build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | /out/ 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | 29 | ### VS Code ### 30 | .vscode/ 31 | -------------------------------------------------------------------------------- /hello-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:17.0.4 2 | 3 | ADD build/libs/hello-service.jar /usr/local/hello-service/ 4 | 5 | WORKDIR /usr/local/hello-service/ 6 | 7 | EXPOSE 8080 8 | 9 | CMD ["java", "-jar", "hello-service.jar"] -------------------------------------------------------------------------------- /hello-service/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'org.springframework.boot' version '3.1.0' 4 | id 'io.spring.dependency-management' version '1.1.0' 5 | } 6 | 7 | apply plugin: "io.spring.dependency-management" 8 | 9 | group = "it.valeriovaudi" 10 | sourceCompatibility = "17" 11 | 12 | jar { 13 | archiveFileName = "hello-service" 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | ext { 21 | set('springCloudVersion', "2022.0.3") 22 | } 23 | 24 | dependencies { 25 | implementation "org.springframework.cloud:spring-cloud-starter-bootstrap" 26 | implementation "org.springframework.cloud:spring-cloud-starter-kubernetes-client-all" 27 | implementation "org.springframework.cloud:spring-cloud-starter-kubernetes-client-loadbalancer" 28 | 29 | implementation 'io.micrometer:micrometer-tracing-bridge-brave' 30 | runtimeOnly 'io.micrometer:micrometer-registry-prometheus' 31 | 32 | implementation "org.springframework.boot:spring-boot-starter-webflux" 33 | implementation "org.springframework.boot:spring-boot-starter-actuator" 34 | } 35 | 36 | dependencyManagement { 37 | imports { 38 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" 39 | } 40 | } -------------------------------------------------------------------------------- /hello-service/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrFlick72/spring-cloud-kubernetes-demo/ceb51f225246ca5ea808e5baef975b358ea86187/hello-service/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /hello-service/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /hello-service/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 | # Stop when "xargs" is not available. 209 | if ! command -v xargs >/dev/null 2>&1 210 | then 211 | die "xargs is not available" 212 | fi 213 | 214 | # Use "xargs" to parse quoted args. 215 | # 216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 217 | # 218 | # In Bash we could simply go: 219 | # 220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 221 | # set -- "${ARGS[@]}" "$@" 222 | # 223 | # but POSIX shell has neither arrays nor command substitution, so instead we 224 | # post-process each arg (as a line of input to sed) to backslash-escape any 225 | # character that might be a shell metacharacter, then use eval to reverse 226 | # that process (while maintaining the separation between arguments), and wrap 227 | # the whole thing up as a single "set" statement. 228 | # 229 | # This will of course break if any of these variables contains a newline or 230 | # an unmatched quote. 231 | # 232 | 233 | eval "set -- $( 234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 235 | xargs -n1 | 236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 237 | tr '\n' ' ' 238 | )" '"$@"' 239 | 240 | exec "$JAVACMD" "$@" -------------------------------------------------------------------------------- /hello-service/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% equ 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% equ 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 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega -------------------------------------------------------------------------------- /hello-service/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "hello-service" -------------------------------------------------------------------------------- /hello-service/src/main/java/it/valeriovaudi/helloservice/HelloServiceApplication.java: -------------------------------------------------------------------------------- 1 | package it.valeriovaudi.helloservice; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.web.reactive.function.client.WebClient; 11 | import org.springframework.web.reactive.function.server.HandlerFunction; 12 | import org.springframework.web.reactive.function.server.RouterFunction; 13 | import org.springframework.web.reactive.function.server.RouterFunctions; 14 | import org.springframework.web.reactive.function.server.ServerResponse; 15 | import reactor.core.publisher.Mono; 16 | 17 | import java.util.HashMap; 18 | import java.util.UUID; 19 | 20 | import static java.lang.String.format; 21 | import static org.springframework.web.reactive.function.BodyInserters.*; 22 | import static org.springframework.web.reactive.function.server.ServerResponse.ok; 23 | import static reactor.core.publisher.Mono.just; 24 | 25 | @SpringBootApplication 26 | public class HelloServiceApplication { 27 | 28 | public static void main(String[] args) { 29 | SpringApplication.run(HelloServiceApplication.class, args); 30 | } 31 | 32 | 33 | @Bean 34 | @LoadBalanced 35 | public WebClient.Builder loadBalancedWebClientBuilder() { 36 | return WebClient.builder(); 37 | } 38 | } 39 | 40 | @Service 41 | class HelloService { 42 | 43 | private static final String DEFAULT_MESSAGE = "no special message for you today :("; 44 | private static final String TEMPLATE = "Hello %s from service instance %s the special message for you to day is %s"; 45 | private static final String INSTANCE_ID = UUID.randomUUID().toString(); 46 | 47 | private final String helloServiceUri; 48 | private final WebClient webClientBuilder; 49 | 50 | HelloService(@Value("${hello-service-uri:}") String helloServiceUri, WebClient.Builder webClientBuilder) { 51 | this.helloServiceUri = helloServiceUri; 52 | this.webClientBuilder = webClientBuilder.build(); 53 | } 54 | 55 | Mono sayHello(String name) { 56 | return webClientBuilder.get() 57 | .uri(helloServiceUri) 58 | .retrieve() 59 | .bodyToMono(HashMap.class) 60 | .flatMap(payload -> just(format(TEMPLATE, name, INSTANCE_ID, payload.getOrDefault("message", DEFAULT_MESSAGE)))); 61 | } 62 | } 63 | 64 | @Configuration 65 | class RouteConfig { 66 | 67 | private final HelloService helloService; 68 | 69 | RouteConfig(HelloService helloService) { 70 | this.helloService = helloService; 71 | } 72 | 73 | @Bean 74 | public RouterFunction routerFunction() { 75 | return RouterFunctions.route() 76 | .GET("/hello/{name}", sayHelloHandler()) 77 | .build(); 78 | } 79 | 80 | private HandlerFunction sayHelloHandler() { 81 | return request -> helloService.sayHello(request.pathVariable("name")) 82 | .flatMap(helloMessage -> ok().body(fromValue(helloMessage))); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /hello-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: hello-service 4 | cloud: 5 | kubernetes: 6 | reload: 7 | enabled: true 8 | mode: polling 9 | period: 5000 10 | 11 | management: 12 | endpoints: 13 | web: 14 | exposure: 15 | include: "*" 16 | endpoint: 17 | restart: 18 | enabled: true 19 | shutdown: 20 | enabled: true 21 | health: 22 | show-details: ALWAYS 23 | server: 24 | port: 8081 -------------------------------------------------------------------------------- /helm/spring-cloud-kubernetes-demo/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /helm/spring-cloud-kubernetes-demo/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: spring-cloud-kubernetes-demo 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | 26 | dependencies: 27 | - name: redis 28 | version: "17.11.3" 29 | repository: "https://charts.bitnami.com/bitnami" 30 | 31 | - name: mongodb 32 | version: 13.1.3 33 | repository: "https://charts.bitnami.com/bitnami" 34 | -------------------------------------------------------------------------------- /helm/spring-cloud-kubernetes-demo/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | it works -------------------------------------------------------------------------------- /helm/spring-cloud-kubernetes-demo/templates/_hello-service-helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Create chart name and version as used by the chart label. 3 | */}} 4 | {{- define "hello-service.chart" -}} 5 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Common labels 10 | */}} 11 | {{- define "hello-service.labels" -}} 12 | helm.sh/chart: {{ include "hello-service.chart" . }} 13 | {{ include "hello-service.selectorLabels" . }} 14 | {{- if .Chart.AppVersion }} 15 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 16 | {{- end }} 17 | app.kubernetes.io/managed-by: {{ .Release.Service }} 18 | {{- end }} 19 | 20 | {{/* 21 | Selector labels 22 | */}} 23 | {{- define "hello-service.selectorLabels" -}} 24 | app.kubernetes.io/name: "hello-service" 25 | app.kubernetes.io/instance: {{ .Release.Name }} 26 | {{- end }} -------------------------------------------------------------------------------- /helm/spring-cloud-kubernetes-demo/templates/_message-service-helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Create chart name and version as used by the chart label. 3 | */}} 4 | {{- define "message-service.chart" -}} 5 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Common labels 10 | */}} 11 | {{- define "message-service.labels" -}} 12 | helm.sh/chart: {{ include "message-service.chart" . }} 13 | {{ include "message-service.selectorLabels" . }} 14 | {{- if .Chart.AppVersion }} 15 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 16 | {{- end }} 17 | app.kubernetes.io/managed-by: {{ .Release.Service }} 18 | {{- end }} 19 | 20 | {{/* 21 | Selector labels 22 | */}} 23 | {{- define "message-service.selectorLabels" -}} 24 | app.kubernetes.io/name: "message-service" 25 | app.kubernetes.io/instance: {{ .Release.Name }} 26 | {{- end }} -------------------------------------------------------------------------------- /helm/spring-cloud-kubernetes-demo/templates/_ui-interface-helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Create chart name and version as used by the chart label. 3 | */}} 4 | {{- define "ui-interface.chart" -}} 5 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Common labels 10 | */}} 11 | {{- define "ui-interface.labels" -}} 12 | helm.sh/chart: {{ include "ui-interface.chart" . }} 13 | {{ include "ui-interface.selectorLabels" . }} 14 | {{- if .Chart.AppVersion }} 15 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 16 | {{- end }} 17 | app.kubernetes.io/managed-by: {{ .Release.Service }} 18 | {{- end }} 19 | 20 | {{/* 21 | Selector labels 22 | */}} 23 | {{- define "ui-interface.selectorLabels" -}} 24 | app.kubernetes.io/name: "ui-interface" 25 | app.kubernetes.io/instance: {{ .Release.Name }} 26 | {{- end }} -------------------------------------------------------------------------------- /helm/spring-cloud-kubernetes-demo/templates/hello-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: hello-service 5 | namespace: {{ .Release.Namespace }} 6 | data: 7 | application.yaml: |- 8 | hello-service-uri: http://message-service/message/random 9 | 10 | --- 11 | 12 | apiVersion: apps/v1 13 | kind: Deployment 14 | metadata: 15 | name: hello-service 16 | namespace: {{ .Release.Namespace }} 17 | labels: 18 | app: hello-service 19 | app.kubernetes.io/name: hello-service 20 | app.kubernetes.io/instance: {{ .Release.Name }} 21 | spec: 22 | replicas: {{ .Values.helloService.replicas }} 23 | selector: 24 | matchLabels: 25 | {{- include "hello-service.selectorLabels" . | nindent 6 }} 26 | template: 27 | metadata: 28 | labels: 29 | {{- include "hello-service.selectorLabels" . | nindent 8 }} 30 | 31 | spec: 32 | containers: 33 | - name: hello-service 34 | image: "mrflick72/hello-service:{{ .Values.helloService.image.tag }}" 35 | imagePullPolicy: {{ .Values.helloService.image.pullPolicy }} 36 | ports: 37 | - containerPort: 8080 38 | - containerPort: 8081 39 | livenessProbe: 40 | httpGet: 41 | path: /actuator/health/liveness 42 | port: 8081 43 | initialDelaySeconds: 10 44 | periodSeconds: 10 45 | successThreshold: 1 46 | 47 | readinessProbe: 48 | httpGet: 49 | path: /actuator/health/readiness 50 | port: 8081 51 | initialDelaySeconds: 10 52 | periodSeconds: 10 53 | successThreshold: 1 54 | 55 | --- 56 | 57 | kind: Service 58 | apiVersion: v1 59 | metadata: 60 | name: hello-service 61 | namespace: {{ .Release.Namespace }} 62 | labels: 63 | {{- include "hello-service.labels" . | nindent 4 }} 64 | spec: 65 | selector: 66 | {{- include "hello-service.selectorLabels" . | nindent 4 }} 67 | ports: 68 | - protocol: TCP 69 | port: 8080 70 | name: http -------------------------------------------------------------------------------- /helm/spring-cloud-kubernetes-demo/templates/message-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: message-service 5 | namespace: {{ .Release.Namespace }} 6 | data: 7 | application.yml: |- 8 | spring: 9 | data: 10 | mongodb: 11 | host: {{ .Release.Namespace }}-mongodb 12 | --- 13 | 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: message-service 18 | namespace: {{ .Release.Namespace }} 19 | labels: 20 | app: message-service 21 | app.kubernetes.io/name: message-service 22 | app.kubernetes.io/instance: {{ .Release.Name }} 23 | spec: 24 | replicas: {{ .Values.messageService.replicas }} 25 | selector: 26 | matchLabels: 27 | {{- include "message-service.selectorLabels" . | nindent 6 }} 28 | template: 29 | metadata: 30 | labels: 31 | {{- include "message-service.selectorLabels" . | nindent 8 }} 32 | 33 | spec: 34 | containers: 35 | - name: message-service 36 | image: "mrflick72/message-service:{{ .Values.messageService.image.tag }}" 37 | imagePullPolicy: {{ .Values.messageService.image.pullPolicy }} 38 | ports: 39 | - containerPort: 8080 40 | - containerPort: 8081 41 | livenessProbe: 42 | httpGet: 43 | path: /actuator/health/liveness 44 | port: 8081 45 | initialDelaySeconds: 10 46 | periodSeconds: 10 47 | successThreshold: 1 48 | 49 | readinessProbe: 50 | httpGet: 51 | path: /actuator/health/readiness 52 | port: 8081 53 | initialDelaySeconds: 10 54 | periodSeconds: 10 55 | successThreshold: 1 56 | --- 57 | 58 | kind: Service 59 | apiVersion: v1 60 | metadata: 61 | name: message-service 62 | namespace: {{ .Release.Namespace }} 63 | labels: 64 | {{- include "message-service.labels" . | nindent 4 }} 65 | spec: 66 | selector: 67 | {{- include "message-service.selectorLabels" . | nindent 4 }} 68 | ports: 69 | - protocol: TCP 70 | port: 8080 71 | name: http 72 | -------------------------------------------------------------------------------- /helm/spring-cloud-kubernetes-demo/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | kind: Role 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | namespace: {{ .Release.Namespace }} 5 | name: namespace-reader 6 | rules: 7 | - apiGroups: [""] 8 | resources: ["configmaps", "pods", "services", "endpoints", "secrets"] 9 | verbs: ["get", "list", "watch"] 10 | 11 | --- 12 | 13 | kind: RoleBinding 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | metadata: 16 | name: namespace-reader-binding 17 | namespace: {{ .Release.Namespace }} 18 | subjects: 19 | - kind: ServiceAccount 20 | name: default 21 | apiGroup: "" 22 | roleRef: 23 | kind: Role 24 | name: namespace-reader 25 | apiGroup: "" -------------------------------------------------------------------------------- /helm/spring-cloud-kubernetes-demo/templates/ui-interface.yml: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: ui-interface 5 | namespace: {{ .Release.Namespace }} 6 | data: 7 | application.yaml: |- 8 | postLogoutUrl: {{ .Values.ui.postLogoutUrl }} 9 | server: 10 | forward-headers-strategy: framework 11 | 12 | spring: 13 | data: 14 | redis: 15 | host: {{ .Release.Namespace }}-redis-master 16 | 17 | 18 | security: 19 | oauth2: 20 | client: 21 | registration: 22 | client: 23 | client-id: {{ .Values.keycloak.sso.clientId }} 24 | client-secret: {{ .Values.keycloak.sso.clientSecret }} 25 | client-name: keycloak 26 | provider: keycloak 27 | scope: 28 | - openid 29 | authorization-grant-type: authorization_code 30 | provider: 31 | keycloak: 32 | issuer-uri: {{ .Values.keycloak.tenantUrl }} 33 | user-name-attribute: preferred_username 34 | 35 | cloud: 36 | gateway: 37 | routes: 38 | - id: hello-service 39 | uri: lb://hello-service/ 40 | predicates: 41 | - Path=/hello-service/** 42 | filters: 43 | - StripPrefix=1 44 | 45 | - id: message-service 46 | uri: lb://message-service/ 47 | predicates: 48 | - Path=/message-service/** 49 | filters: 50 | - StripPrefix=1 51 | --- 52 | 53 | apiVersion: apps/v1 54 | kind: Deployment 55 | metadata: 56 | name: ui-interface 57 | namespace: {{ .Release.Namespace }} 58 | labels: 59 | app: ui-interface 60 | app.kubernetes.io/name: ui-interface 61 | app.kubernetes.io/instance: {{ .Release.Name }} 62 | spec: 63 | replicas: {{ .Values.ui.replicas }} 64 | selector: 65 | matchLabels: 66 | {{- include "ui-interface.selectorLabels" . | nindent 6 }} 67 | template: 68 | metadata: 69 | labels: 70 | {{- include "ui-interface.selectorLabels" . | nindent 8 }} 71 | 72 | spec: 73 | containers: 74 | - name: ui-interface 75 | image: "mrflick72/ui-interface:{{ .Values.ui.image.tag }}" 76 | imagePullPolicy: {{ .Values.ui.image.pullPolicy }} 77 | ports: 78 | - containerPort: 8080 79 | - containerPort: 8081 80 | livenessProbe: 81 | httpGet: 82 | path: /actuator/health/liveness 83 | port: 8081 84 | initialDelaySeconds: 10 85 | periodSeconds: 10 86 | successThreshold: 1 87 | 88 | readinessProbe: 89 | httpGet: 90 | path: /actuator/health/readiness 91 | port: 8081 92 | initialDelaySeconds: 10 93 | periodSeconds: 10 94 | successThreshold: 1 95 | --- 96 | 97 | kind: Service 98 | apiVersion: v1 99 | metadata: 100 | name: ui-interface 101 | namespace: {{ .Release.Namespace }} 102 | labels: 103 | {{- include "ui-interface.labels" . | nindent 4 }} 104 | spec: 105 | selector: 106 | {{- include "ui-interface.selectorLabels" . | nindent 4 }} 107 | ports: 108 | - protocol: TCP 109 | port: 8080 110 | name: http 111 | --- 112 | 113 | apiVersion: networking.k8s.io/v1 114 | kind: Ingress 115 | metadata: 116 | name: ui-interface-ingress 117 | namespace: {{ .Release.Namespace }} 118 | annotations: 119 | kubernetes.io/ingress.class: {{ .Values.ui.ingress.class }} 120 | {{- with .Values.ui.ingress.annotations }} 121 | {{- toYaml . | nindent 4 }} 122 | {{- end }} 123 | 124 | spec: 125 | rules: 126 | - http: 127 | paths: 128 | - path: / 129 | pathType: Prefix 130 | backend: 131 | service: 132 | name: ui-interface 133 | port: 134 | number: 8080 135 | host: {{ .Values.ui.ingress.host }} 136 | {{- with .Values.ui.ingress.tls }} 137 | tls: 138 | {{- toYaml . | nindent 4}} 139 | {{- end }} 140 | -------------------------------------------------------------------------------- /helm/spring-cloud-kubernetes-demo/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for spring-cloud-kubernetes-demo. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | messageService: 6 | replicas: 2 7 | image: 8 | pullPolicy: Always 9 | tag: latest 10 | 11 | helloService: 12 | replicas: 2 13 | image: 14 | pullPolicy: Always 15 | tag: latest 16 | 17 | keycloak: 18 | tenantUrl: http://keycloak.local 19 | sso: 20 | clientId: xxx 21 | clientSecret: xxx 22 | 23 | ui: 24 | postLogoutUrl: http://localhost:8080 25 | replicas: 2 26 | image: 27 | pullPolicy: Always 28 | tag: latest 29 | ingress: 30 | tls: { } 31 | host: "*" 32 | annotations: { } 33 | class: nginx 34 | 35 | redis: 36 | auth: 37 | enabled: false 38 | replica: 39 | replicaCount: 1 40 | 41 | mongodb: 42 | auth: 43 | enabled: false 44 | persistence: 45 | storageClass: manual 46 | accessModes: 47 | - ReadWriteMany 48 | -------------------------------------------------------------------------------- /images/messages_webapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrFlick72/spring-cloud-kubernetes-demo/ceb51f225246ca5ea808e5baef975b358ea86187/images/messages_webapp.png -------------------------------------------------------------------------------- /images/user_webapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrFlick72/spring-cloud-kubernetes-demo/ceb51f225246ca5ea808e5baef975b358ea86187/images/user_webapp.png -------------------------------------------------------------------------------- /message-service/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | /build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | /out/ 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | 29 | ### VS Code ### 30 | .vscode/ 31 | -------------------------------------------------------------------------------- /message-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:17.0.4 2 | 3 | ADD build/libs/message-service.jar /usr/local/message-service/ 4 | 5 | WORKDIR /usr/local/message-service/ 6 | 7 | EXPOSE 8080 8 | 9 | CMD ["java", "-jar", "message-service.jar"] -------------------------------------------------------------------------------- /message-service/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | id("org.springframework.boot") version "3.1.0" 5 | id("io.spring.dependency-management") version "1.1.0" 6 | kotlin("jvm") version "1.8.21" 7 | kotlin("plugin.spring") version "1.8.21" 8 | } 9 | 10 | group = "it.valeriovaudi" 11 | java.sourceCompatibility = JavaVersion.VERSION_17 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | extra["springCloudVersion"] = "2022.0.3" 18 | 19 | dependencies { 20 | implementation("org.springframework.cloud:spring-cloud-starter-bootstrap") 21 | implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive") 22 | implementation("org.springframework.boot:spring-boot-starter-webflux") 23 | implementation("org.springframework.boot:spring-boot-starter-actuator") 24 | implementation("org.springframework.cloud:spring-cloud-starter-kubernetes-client-all") 25 | 26 | implementation("com.fasterxml.jackson.module:jackson-module-kotlin") 27 | implementation("org.jetbrains.kotlin:kotlin-reflect") 28 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") 29 | implementation("org.jetbrains.kotlin:kotlin-reflect") 30 | 31 | implementation("io.micrometer:micrometer-tracing-bridge-brave") 32 | implementation("io.projectreactor.kotlin:reactor-kotlin-extensions") 33 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") 34 | runtimeOnly("io.micrometer:micrometer-registry-prometheus") 35 | } 36 | 37 | 38 | dependencyManagement { 39 | imports { 40 | mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}") 41 | } 42 | } 43 | 44 | tasks.withType { 45 | kotlinOptions { 46 | freeCompilerArgs = listOf("-Xjsr305=strict") 47 | jvmTarget = "17" 48 | } 49 | } -------------------------------------------------------------------------------- /message-service/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrFlick72/spring-cloud-kubernetes-demo/ceb51f225246ca5ea808e5baef975b358ea86187/message-service/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /message-service/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /message-service/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 | # Stop when "xargs" is not available. 209 | if ! command -v xargs >/dev/null 2>&1 210 | then 211 | die "xargs is not available" 212 | fi 213 | 214 | # Use "xargs" to parse quoted args. 215 | # 216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 217 | # 218 | # In Bash we could simply go: 219 | # 220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 221 | # set -- "${ARGS[@]}" "$@" 222 | # 223 | # but POSIX shell has neither arrays nor command substitution, so instead we 224 | # post-process each arg (as a line of input to sed) to backslash-escape any 225 | # character that might be a shell metacharacter, then use eval to reverse 226 | # that process (while maintaining the separation between arguments), and wrap 227 | # the whole thing up as a single "set" statement. 228 | # 229 | # This will of course break if any of these variables contains a newline or 230 | # an unmatched quote. 231 | # 232 | 233 | eval "set -- $( 234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 235 | xargs -n1 | 236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 237 | tr '\n' ' ' 238 | )" '"$@"' 239 | 240 | exec "$JAVACMD" "$@" -------------------------------------------------------------------------------- /message-service/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% equ 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% equ 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 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega -------------------------------------------------------------------------------- /message-service/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "message-service" -------------------------------------------------------------------------------- /message-service/src/main/kotlin/it/valeriovaudi/messageservice/MessageServiceApplication.kt: -------------------------------------------------------------------------------- 1 | package it.valeriovaudi.messageservice 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | import org.springframework.boot.runApplication 5 | import org.springframework.context.annotation.Bean 6 | import org.springframework.context.annotation.Configuration 7 | import org.springframework.data.annotation.Id 8 | import org.springframework.data.mongodb.repository.ReactiveMongoRepository 9 | import org.springframework.web.reactive.function.BodyInserters.fromValue 10 | import org.springframework.web.reactive.function.server.router 11 | import org.springframework.web.util.UriComponentsBuilder 12 | import java.util.* 13 | 14 | @SpringBootApplication 15 | class MessageServiceApplication 16 | 17 | fun main(args: Array) { 18 | runApplication(*args) 19 | } 20 | 21 | data class Message(@Id var id: String? = null, var message: String = "") 22 | 23 | interface MessageRepository : ReactiveMongoRepository 24 | 25 | @Configuration 26 | class MessageRoute(private val messageRepository: MessageRepository) { 27 | 28 | @Bean 29 | fun route() = router { 30 | 31 | GET("/message") { 32 | messageRepository.findAll() 33 | .collectList() 34 | .flatMap { ok().body(fromValue(it)) } 35 | } 36 | 37 | GET("/message/random") { 38 | messageRepository.findAll() 39 | .collectList() 40 | .flatMap { 41 | if (it.size != 0) { 42 | ok().body(fromValue(it[Random().nextInt(it.size)])) 43 | } else { 44 | notFound().build() 45 | } 46 | } 47 | } 48 | 49 | GET("/message/{messageId}") { 50 | messageRepository.findById(it.pathVariable("messageId")) 51 | .flatMap { ok().body(fromValue(it)) } 52 | } 53 | 54 | POST("/message") { 55 | it.bodyToMono(Message::class.java) 56 | .flatMap { messageRepository.save(it) } 57 | .flatMap { created(UriComponentsBuilder.fromPath("/message/${it.id}").build().toUri()).build() } 58 | } 59 | 60 | PUT("/message/{messageId}") { 61 | val messageId = it.pathVariable("messageId") 62 | it.bodyToMono(Message::class.java) 63 | .flatMap { 64 | it.let { 65 | it.id = messageId 66 | messageRepository.save(it) 67 | } 68 | } 69 | .flatMap { noContent().build() } 70 | } 71 | 72 | DELETE("/message/{messageId}") { 73 | val messageId = it.pathVariable("messageId") 74 | messageRepository.deleteById(messageId) 75 | .flatMap { noContent().build() } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /message-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: message-service 4 | cloud: 5 | kubernetes: 6 | reload: 7 | enabled: true 8 | mode: polling 9 | period: 5000 10 | 11 | management: 12 | endpoints: 13 | web: 14 | exposure: 15 | include: "*" 16 | endpoint: 17 | restart: 18 | enabled: true 19 | shutdown: 20 | enabled: true 21 | health: 22 | show-details: ALWAYS 23 | server: 24 | port: 8081 -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | 28 | ### VS Code ### 29 | .vscode/ 30 | -------------------------------------------------------------------------------- /ui/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. 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, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if (mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if (mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if (!outputFile.getParentFile().exists()) { 87 | if (!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /ui/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrFlick72/spring-cloud-kubernetes-demo/ceb51f225246ca5ea808e5baef975b358ea86187/ui/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /ui/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /ui/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM amazoncorretto:17.0.4 2 | 3 | ADD target/ui-interface.jar /usr/local/ui-interface/ 4 | 5 | WORKDIR /usr/local/ui-interface/ 6 | 7 | EXPOSE 8080 8 | 9 | CMD ["java", "-jar", "ui-interface.jar"] -------------------------------------------------------------------------------- /ui/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | ########################################################################################## 204 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 205 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 206 | ########################################################################################## 207 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found .mvn/wrapper/maven-wrapper.jar" 210 | fi 211 | else 212 | if [ "$MVNW_VERBOSE" = true ]; then 213 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 214 | fi 215 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 216 | while IFS="=" read key value; do 217 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 218 | esac 219 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Downloading from: $jarUrl" 222 | fi 223 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 224 | 225 | if command -v wget > /dev/null; then 226 | if [ "$MVNW_VERBOSE" = true ]; then 227 | echo "Found wget ... using wget" 228 | fi 229 | wget "$jarUrl" -O "$wrapperJarPath" 230 | elif command -v curl > /dev/null; then 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Found curl ... using curl" 233 | fi 234 | curl -o "$wrapperJarPath" "$jarUrl" 235 | else 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Falling back to using Java to download" 238 | fi 239 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 240 | if [ -e "$javaClass" ]; then 241 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 242 | if [ "$MVNW_VERBOSE" = true ]; then 243 | echo " - Compiling MavenWrapperDownloader.java ..." 244 | fi 245 | # Compiling the Java class 246 | ("$JAVA_HOME/bin/javac" "$javaClass") 247 | fi 248 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 249 | # Running the downloader 250 | if [ "$MVNW_VERBOSE" = true ]; then 251 | echo " - Running MavenWrapperDownloader.java ..." 252 | fi 253 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 254 | fi 255 | fi 256 | fi 257 | fi 258 | ########################################################################################## 259 | # End of extension 260 | ########################################################################################## 261 | 262 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo $MAVEN_PROJECTBASEDIR 265 | fi 266 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 267 | 268 | # For Cygwin, switch paths to Windows format before running java 269 | if $cygwin; then 270 | [ -n "$M2_HOME" ] && 271 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 272 | [ -n "$JAVA_HOME" ] && 273 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 274 | [ -n "$CLASSPATH" ] && 275 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 276 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 277 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 278 | fi 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 285 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 286 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 287 | -------------------------------------------------------------------------------- /ui/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /ui/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.1.0 9 | 10 | 11 | it.valeriovaudi 12 | ui 13 | 1.0.0-SNAPSHOT 14 | ui 15 | Demo project for Spring Boot 16 | 17 | 18 | 17 19 | 2022.0.3 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-oauth2-client 26 | 27 | 28 | org.springframework.cloud 29 | spring-cloud-starter-bootstrap 30 | 31 | 32 | io.micrometer 33 | micrometer-tracing-bridge-brave 34 | 35 | 36 | io.micrometer 37 | micrometer-registry-prometheus 38 | runtime 39 | 40 | 41 | org.springframework.cloud 42 | spring-cloud-starter-kubernetes-client-all 43 | 44 | 45 | 46 | org.springframework.cloud 47 | spring-cloud-starter-kubernetes-client-loadbalancer 48 | 49 | 50 | 51 | org.springframework.session 52 | spring-session-core 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-data-redis 58 | 59 | 60 | 61 | org.springframework.session 62 | spring-session-data-redis 63 | 64 | 65 | 66 | org.springframework.cloud 67 | spring-cloud-starter-gateway 68 | 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-starter-actuator 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-starter-security 78 | 79 | 80 | 81 | 82 | 83 | 84 | org.springframework.cloud 85 | spring-cloud-dependencies 86 | ${spring-cloud.version} 87 | pom 88 | import 89 | 90 | 91 | 92 | 93 | 94 | ui-interface 95 | 96 | 97 | 98 | org.springframework.boot 99 | spring-boot-maven-plugin 100 | 101 | 102 | 103 | com.github.eirslett 104 | frontend-maven-plugin 105 | 1.6 106 | 107 | 108 | src/main/frontend 109 | 110 | 111 | 112 | 113 | install node and npm 114 | 115 | install-node-and-npm 116 | 117 | 118 | v9.8.0 119 | 5.8.0 120 | 121 | 122 | 123 | 124 | npm install 125 | 126 | npm 127 | 128 | 129 | install 130 | 131 | 132 | 133 | 134 | npm build 135 | 136 | npm 137 | 138 | 139 | run-script build 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /ui/src/main/frontend/app/component/Container.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export default ({children}) => ( 4 |
5 | {children} 6 |
7 | ); -------------------------------------------------------------------------------- /ui/src/main/frontend/app/component/Jumbotron.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export default ({title, leadSection, bottomSection}) => { 4 | return
5 |

{title}

6 | 7 |
8 | {leadSection} 9 |
10 | 11 |
12 | 13 | {bottomSection} 14 |
15 | } 16 | -------------------------------------------------------------------------------- /ui/src/main/frontend/app/component/NavBar.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export default ({title}) => ( 4 |
5 | 18 |
19 | ); -------------------------------------------------------------------------------- /ui/src/main/frontend/app/component/TextInputForm.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export default ({value, onChangeHandler, componentId, componentLabel, componentPlaceholder, inputRef}) => { 4 | return
5 | 6 | 10 |
11 | } -------------------------------------------------------------------------------- /ui/src/main/frontend/app/messages-site/MessageSiteApp.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Jumbotron from "../component/Jumbotron"; 3 | import MessageRepository from "../repository/MessageRepository"; 4 | import TextInputForm from "../component/TextInputForm"; 5 | import NavBar from "../component/NavBar"; 6 | import Container from "../component/Container"; 7 | 8 | export default class MessageSiteApp extends React.Component { 9 | 10 | constructor(props) { 11 | super(props) 12 | 13 | this.state = { 14 | messages: [] 15 | }; 16 | 17 | this.inputRef = React.createRef(); 18 | this.messageRepository = new MessageRepository(); 19 | this.saveMessage = this.saveMessage.bind(this); 20 | this.displayMessages = this.displayMessages.bind(this); 21 | } 22 | 23 | saveMessage() { 24 | this.messageRepository 25 | .saveMessage({message: this.inputRef.current.value}) 26 | .then(response => this.displayMessages()) 27 | } 28 | 29 | componentDidMount() { 30 | this.displayMessages(); 31 | } 32 | 33 | displayMessages() { 34 | this.messageRepository.findMessages() 35 | .then(data => { 36 | console.log(data) 37 | this.setState({messages: data}) 38 | }) 39 | } 40 | 41 | deleteMessage(messageId) { 42 | this.messageRepository.deleteMessage(messageId) 43 | .then(response => { 44 | this.displayMessages() 45 | }) 46 | } 47 | 48 | render() { 49 | let leadSection =
50 | 54 | 55 | 56 | 57 | let bottomSection = 58 |
    59 | {this.state.messages.map(message => { 60 | return
  • 62 | {message.message} 63 | 65 | Delete 66 | 67 |
  • 68 | })} 69 |
70 | return 71 | 72 | 73 | 77 | 78 | 79 | } 80 | } -------------------------------------------------------------------------------- /ui/src/main/frontend/app/messages-site/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import MessageSiteApp from "./MessageSiteApp"; 4 | 5 | if(document.getElementById('app')){ 6 | ReactDOM.render(, document.getElementById('app')); 7 | } -------------------------------------------------------------------------------- /ui/src/main/frontend/app/repository/MessageRepository.js: -------------------------------------------------------------------------------- 1 | const SAY_HELLO_TO = (name) => `/hello-service/hello/${name}`; 2 | const SAVE_A_NEW_MESSAGE = "/message-service/message"; 3 | const DELETE_A_MESSAGE = (messageId) => `/message-service/message/${messageId}`; 4 | 5 | export default class MessageRepository { 6 | 7 | sayHelloTo(name) { 8 | return fetch(SAY_HELLO_TO(name)) 9 | .then(data => data.text()); 10 | } 11 | 12 | saveMessage(message) { 13 | return fetch(SAVE_A_NEW_MESSAGE, { 14 | method: "POST", 15 | headers: { 16 | 'Content-Type': 'application/json' 17 | }, 18 | body: JSON.stringify(message), 19 | credentials: 'same-origin' 20 | }) 21 | } 22 | 23 | findMessages() { 24 | return fetch(SAVE_A_NEW_MESSAGE, { 25 | method: "GET", 26 | headers: { 27 | 'Accept': 'application/json' 28 | }, 29 | credentials: 'same-origin' 30 | }).then(response => response.json()) 31 | } 32 | 33 | 34 | deleteMessage(messageId) { 35 | return fetch(DELETE_A_MESSAGE(messageId), { 36 | method: "DELETE", 37 | credentials: 'same-origin' 38 | }) 39 | } 40 | } -------------------------------------------------------------------------------- /ui/src/main/frontend/app/site/MainSiteApp.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Jumbotron from "../component/Jumbotron"; 3 | import MessageRepository from "../repository/MessageRepository"; 4 | import TextInputForm from "../component/TextInputForm"; 5 | import NavBar from "../component/NavBar"; 6 | import Container from "../component/Container"; 7 | 8 | export default class MainSiteApp extends React.Component { 9 | 10 | constructor(props) { 11 | super(props) 12 | 13 | this.state = { 14 | message: "No message right now" 15 | }; 16 | 17 | this.inputRef = React.createRef(); 18 | this.messageRepository = new MessageRepository(); 19 | this.sayHello = this.sayHello.bind(this); 20 | } 21 | 22 | sayHello() { 23 | this.messageRepository 24 | .sayHelloTo(this.inputRef.current.value) 25 | .then(message => this.setState({message: message})) 26 | } 27 | 28 | render() { 29 | let leadSection =
30 | 34 | 35 | 36 | let bottomSection =

{this.state.message}

37 | 38 | return 39 | 40 | 41 | 46 | 47 | 48 | } 49 | } -------------------------------------------------------------------------------- /ui/src/main/frontend/app/site/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import MainSiteApp from "./MainSiteApp"; 4 | 5 | if(document.getElementById('app')){ 6 | ReactDOM.render(, document.getElementById('app')); 7 | } -------------------------------------------------------------------------------- /ui/src/main/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "version": "1.0.0", 4 | "description": "UI of My say hello on kubernetes", 5 | "babel": { 6 | "presets": [ 7 | "react", 8 | "env" 9 | ] 10 | }, 11 | "scripts": { 12 | "build": "webpack --config webpack.config.js", 13 | "watch": "webpack --watch" 14 | }, 15 | "author": "mrFlick72", 16 | "license": "ISC", 17 | "devDependencies": { 18 | "babel-core": "^6.26.2", 19 | "babel-preset-env": "^1.6.1", 20 | "babel-preset-react": "^6.24.1", 21 | "clean-webpack-plugin": "^0.1.19", 22 | "css-loader": "^0.28.11", 23 | "html-webpack-plugin": "^3.2.0", 24 | "uglifyjs-webpack-plugin": "^2.1.1", 25 | "webpack": "^4.29.0", 26 | "webpack-cli": "^3.3.1" 27 | }, 28 | "dependencies": { 29 | "acorn": "^6.1.1", 30 | "babel-loader": "^7.1.4", 31 | "build": "^0.1.4", 32 | "prop-types": "^15.6.1", 33 | "react": "^16.3.2", 34 | "react-dom": "^16.3.2", 35 | "style-loader": "^0.21.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ui/src/main/frontend/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | const BUID_DIR = path.resolve(__dirname + "../../../../target/classes/static"); 5 | 6 | module.exports = { 7 | // mode: 'production', 8 | entry: { 9 | site: path.resolve(__dirname, './app/site/index.js'), 10 | messagesSite: path.resolve(__dirname, './app/messages-site/index.js') 11 | }, 12 | resolve: { 13 | extensions: [".js", ".jsx"] 14 | }, 15 | plugins: [ 16 | new HtmlWebpackPlugin({ 17 | chunks: ['site'], 18 | filename: "index.html", 19 | template: path.resolve(__dirname, "../resources/static/index.html") 20 | }), 21 | new HtmlWebpackPlugin({ 22 | chunks: ['messagesSite'], 23 | filename: "messages.html", 24 | template: path.resolve(__dirname, "../resources/static/messages.html") 25 | }) 26 | ], 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.css$/, 31 | use: ['style-loader', 'css-loader'] 32 | }, 33 | { 34 | test: path.join(__dirname, "."), 35 | exclude: path.resolve(__dirname, "node_modules"), 36 | use: { 37 | loader: "babel-loader", 38 | options: { 39 | presets: ["env", "react"] 40 | } 41 | } 42 | 43 | } 44 | ] 45 | }, 46 | output: { 47 | filename: '[name]_[hash]_bundle.js', 48 | path: BUID_DIR 49 | } 50 | }; -------------------------------------------------------------------------------- /ui/src/main/java/it/valeriovaudi/ui/UiApplication.java: -------------------------------------------------------------------------------- 1 | package it.valeriovaudi.ui; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.config.Customizer; 9 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 10 | import org.springframework.security.config.web.server.ServerHttpSecurity; 11 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 12 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService; 13 | import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; 14 | import org.springframework.security.oauth2.client.oidc.web.server.logout.OidcClientInitiatedServerLogoutSuccessHandler; 15 | import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; 16 | import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService; 17 | import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; 18 | import org.springframework.security.oauth2.core.oidc.user.OidcUser; 19 | import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; 20 | import org.springframework.security.web.server.SecurityWebFilterChain; 21 | import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler; 22 | import reactor.core.publisher.Mono; 23 | 24 | import java.util.List; 25 | import java.util.Set; 26 | import java.util.stream.Collectors; 27 | 28 | @SpringBootApplication 29 | public class UiApplication { 30 | 31 | public static void main(String[] args) { 32 | SpringApplication.run(UiApplication.class, args); 33 | } 34 | 35 | } 36 | 37 | @EnableWebFluxSecurity 38 | @Configuration(proxyBeanMethods = false) 39 | class SecurityConfig { 40 | 41 | private final ReactiveClientRegistrationRepository clientRegistrationRepository; 42 | 43 | SecurityConfig(ReactiveClientRegistrationRepository clientRegistrationRepository) { 44 | this.clientRegistrationRepository = clientRegistrationRepository; 45 | } 46 | 47 | @Bean 48 | public ReactiveOAuth2UserService oidcUserService() { 49 | final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService(); 50 | 51 | 52 | return (userRequest) -> { 53 | // Delegate to the default implementation for loading a user 54 | return delegate.loadUser(userRequest) 55 | .flatMap((oidcUser) -> { 56 | List authorities = (List) oidcUser.getClaimAsMap("realm_access").get("roles"); 57 | Set oidcAuthorities = authorities.stream() 58 | .map(SimpleGrantedAuthority::new) 59 | .map(authority -> new OidcUserAuthority(authority.getAuthority(), oidcUser.getIdToken(), oidcUser.getUserInfo())) 60 | .collect(Collectors.toSet()); 61 | return Mono.just(new DefaultOidcUser(oidcAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo())); 62 | }); 63 | }; 64 | 65 | } 66 | 67 | @Bean 68 | public SecurityWebFilterChain defaultSecurityFilterChain( 69 | @Value("${postLogoutUrl}") String postLogoutUrl, 70 | ServerHttpSecurity http) { 71 | http.csrf(ServerHttpSecurity.CsrfSpec::disable); 72 | http.headers(configurer -> configurer.frameOptions(ServerHttpSecurity.HeaderSpec.FrameOptionsSpec::disable)); 73 | 74 | http.oauth2Login(Customizer.withDefaults()); 75 | http.logout(logoutSpec -> { 76 | logoutSpec.logoutSuccessHandler(oidcLogoutSuccessHandler(postLogoutUrl, clientRegistrationRepository)); 77 | }); 78 | 79 | http.authorizeExchange( 80 | auth -> 81 | auth 82 | .pathMatchers("/").hasAuthority("USER") 83 | .pathMatchers("/index.html").hasAuthority("USER") 84 | .pathMatchers("/messages.html").hasAuthority("ADMIN") 85 | .anyExchange().permitAll() 86 | ); 87 | 88 | return http.build(); 89 | } 90 | 91 | private ServerLogoutSuccessHandler oidcLogoutSuccessHandler( 92 | String postLogoutUrl, 93 | ReactiveClientRegistrationRepository clientRegistrationRepository 94 | ) { 95 | OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler = 96 | new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository); 97 | oidcLogoutSuccessHandler.setPostLogoutRedirectUri(postLogoutUrl); 98 | return oidcLogoutSuccessHandler; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ui/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: ui-interface 4 | cloud: 5 | kubernetes: 6 | reload: 7 | enabled: true 8 | mode: polling 9 | period: 5000 10 | 11 | management: 12 | endpoints: 13 | web: 14 | exposure: 15 | include: "*" 16 | endpoint: 17 | restart: 18 | enabled: true 19 | shutdown: 20 | enabled: true 21 | health: 22 | show-details: ALWAYS 23 | server: 24 | port: 8081 -------------------------------------------------------------------------------- /ui/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ui Interface 6 | 7 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 19 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ui/src/main/resources/static/messages.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ui Interface 6 | 7 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 19 | 22 | 25 | 26 | 27 | 28 | --------------------------------------------------------------------------------