├── .gitignore ├── .travis.yml ├── .vscode └── launch.json ├── 00-provisioning ├── README.md ├── access-gke.sh ├── create-gke.sh └── delete-gke.sh ├── 01-config-and-secrets ├── README.md ├── hello-spring-cloud-kubernetes │ ├── .gitignore │ ├── Dockerfile │ ├── LICENSE │ ├── README.md │ ├── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── k8s │ │ ├── config_secrets.sh │ │ ├── configmap-properties.yaml │ │ ├── configmap-yaml.yaml │ │ ├── configmap.yaml │ │ ├── deploy.sh │ │ ├── deployment.yaml │ │ ├── read-access-role.yaml │ │ ├── rolebinding-read-access.yaml │ │ ├── secrets.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.sh │ │ └── serviceaccount.yaml │ ├── settings.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── hello │ │ │ │ ├── HelloController.java │ │ │ │ ├── HelloSpringCloudKubernetesApplication.java │ │ │ │ ├── HelloUserService.java │ │ │ │ ├── Message.java │ │ │ │ └── MessageConfig.java │ │ └── resources │ │ │ ├── application.yaml │ │ │ └── bootstrap.yaml │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── hello │ │ └── HelloControllerTests.java └── hello-spring-kubernetes │ ├── .gitignore │ ├── Dockerfile │ ├── LICENSE │ ├── README.md │ ├── build.gradle │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── k8s │ ├── config_secrets.sh │ ├── configmap.yaml │ ├── deploy.sh │ ├── deployment.yaml │ ├── secrets.yaml │ └── service.yaml │ ├── settings.gradle │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── hello │ │ │ ├── HelloController.java │ │ │ ├── HelloSpringKubernetesApplication.java │ │ │ ├── HelloUserService.java │ │ │ ├── Message.java │ │ │ └── MessageConfig.java │ └── resources │ │ └── application.yaml │ └── test │ └── java │ └── com │ └── example │ └── hello │ └── HelloControllerTests.java ├── 02-discovery ├── README.md ├── answering-service │ ├── .gitignore │ ├── Dockerfile │ ├── bin │ │ └── main │ │ │ └── application.properties │ ├── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── k8s │ │ ├── deploy.sh │ │ ├── deployment.yaml │ │ └── service.yaml │ ├── settings.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── discovery │ │ │ │ └── server │ │ │ │ ├── AnsweringRestController.java │ │ │ │ └── AnsweringServiceApplication.java │ │ └── resources │ │ │ ├── application.yaml │ │ │ └── bootstrap.yaml │ │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── discovery │ │ └── server │ │ └── AnsweringServiceApplicationTests.java └── question-client │ ├── .gitignore │ ├── Dockerfile │ ├── bin │ └── main │ │ └── application.properties │ ├── build.gradle │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── k8s │ ├── deploy.sh │ ├── deployment.yaml │ ├── read-access-role.yaml │ ├── rolebinding-read-access.yaml │ ├── service.yaml │ ├── serviceaccount.sh │ └── serviceaccount.yaml │ ├── settings.gradle │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── discovery │ │ │ └── client │ │ │ ├── QuestionClientApplication.java │ │ │ ├── QuestionRestController.java │ │ │ └── WebClientConfiguration.java │ └── resources │ │ ├── application.yaml │ │ └── bootstrap.yaml │ └── test │ └── java │ └── com │ └── example │ └── discovery │ └── client │ └── QuestionClientApplicationTests.java ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── css │ ├── print │ │ ├── paper.css │ │ └── pdf.css │ ├── reset.css │ ├── reveal.css │ ├── reveal.scss │ └── theme │ │ ├── README.md │ │ ├── beige.css │ │ ├── black.css │ │ ├── blood.css │ │ ├── images │ │ ├── nt_logo_small.png │ │ └── spring_io_logo.png │ │ ├── league.css │ │ ├── moon.css │ │ ├── night.css │ │ ├── novatec.css │ │ ├── serif.css │ │ ├── simple.css │ │ ├── sky.css │ │ ├── solarized.css │ │ ├── source │ │ ├── beige.scss │ │ ├── black.scss │ │ ├── blood.scss │ │ ├── league.scss │ │ ├── moon.scss │ │ ├── night.scss │ │ ├── novatec.scss │ │ ├── serif.scss │ │ ├── simple.scss │ │ ├── sky.scss │ │ ├── solarized.scss │ │ ├── spring_io.scss │ │ └── white.scss │ │ ├── spring_io.css │ │ ├── template │ │ ├── mixins.scss │ │ ├── settings.scss │ │ └── theme.scss │ │ └── white.css ├── demo.html ├── gruntfile.js ├── images │ ├── agile-security-book.jpg │ ├── alpine_docker_cve.png │ ├── andreas_falk.jpg │ ├── cropped-novatec-favicon-192x192.png │ ├── cropped-novatec-favicon-32x32.png │ ├── devops-handbook.jpg │ ├── docker_images_vulnerabilities.png │ ├── encryption_layers.png │ ├── encryption_layers.xml │ ├── envelope_encryption.png │ ├── envelope_encryption.xml │ ├── envelope_encryption_retrieve.svg │ ├── helm_3_tweet.png │ ├── iron-glad-java.jpg │ ├── kubernetes_basics.png │ ├── novatec-logo.png │ ├── novatec_logo_big.png │ ├── novatec_offices.png │ ├── oauth2_in_action.jpg │ ├── owasp-logo.png │ ├── secrets_env_vars_tweet.png │ ├── spring-logo.png │ ├── spring_io_2019_workshop.jpg │ ├── spring_io_kubernetes_cloud.png │ ├── threat-modeling-book.jpg │ ├── title_image.png │ └── tweet_kubernetes_all_root.png ├── index.html ├── js │ └── reveal.js ├── lib │ ├── css │ │ ├── monokai.css │ │ └── zenburn.css │ ├── font │ │ ├── league-gothic │ │ │ ├── LICENSE │ │ │ ├── league-gothic.css │ │ │ ├── league-gothic.eot │ │ │ ├── league-gothic.ttf │ │ │ └── league-gothic.woff │ │ └── source-sans-pro │ │ │ ├── LICENSE │ │ │ ├── source-sans-pro-italic.eot │ │ │ ├── source-sans-pro-italic.ttf │ │ │ ├── source-sans-pro-italic.woff │ │ │ ├── source-sans-pro-regular.eot │ │ │ ├── source-sans-pro-regular.ttf │ │ │ ├── source-sans-pro-regular.woff │ │ │ ├── source-sans-pro-semibold.eot │ │ │ ├── source-sans-pro-semibold.ttf │ │ │ ├── source-sans-pro-semibold.woff │ │ │ ├── source-sans-pro-semibolditalic.eot │ │ │ ├── source-sans-pro-semibolditalic.ttf │ │ │ ├── source-sans-pro-semibolditalic.woff │ │ │ └── source-sans-pro.css │ └── js │ │ ├── html5shiv.js │ │ └── promise.js ├── package.json └── plugin │ ├── elapsed-time-bar │ └── elapsed-time-bar.js │ ├── highlight │ └── highlight.js │ ├── markdown │ ├── example.html │ ├── example.md │ ├── markdown.js │ └── marked.js │ ├── math │ └── math.js │ ├── multiplex │ ├── client.js │ ├── index.js │ ├── master.js │ └── package.json │ ├── notes-server │ ├── client.js │ ├── index.js │ └── notes.html │ ├── notes │ ├── notes.html │ └── notes.js │ ├── print-pdf │ └── print-pdf.js │ ├── search │ └── search.js │ ├── tagcloud │ └── tagcloud.js │ └── zoom-js │ └── zoom.js ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.war 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | .idea/ 25 | .vscode/ 26 | .gradle/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | dist: trusty 3 | install: skip 4 | sudo: required 5 | services: 6 | - docker 7 | 8 | jobs: 9 | include: 10 | 11 | - stage: build 12 | jdk: openjdk8 13 | script: 14 | - ./gradlew build 15 | - stage: build 16 | jdk: oraclejdk9 17 | script: 18 | - ./gradlew build 19 | - stage: build 20 | jdk: openjdk11 21 | script: 22 | - ./gradlew build -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "java", 5 | "name": "CodeLens (Launch) - DemoApplication", 6 | "request": "launch", 7 | "mainClass": "com.example.discovery1.DemoApplication" 8 | }, 9 | { 10 | "type": "java", 11 | "name": "Spring Boot-DemoApplication", 12 | "request": "launch", 13 | "cwd": "${workspaceFolder}", 14 | "console": "internalConsole", 15 | "mainClass": "com.example.hello.DemoApplication", 16 | "projectName": "hello-spring-kubernetes", 17 | "args": "" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /00-provisioning/README.md: -------------------------------------------------------------------------------- 1 | # Demo K8s cluster on Google Cloud 2 | 3 | Her you find helpful scripts to setup & access 4 | GKE on Google Cloud. 5 | 6 | Note: Please replace "pa-afa-kubernetes" with your 7 | own project on GCP in all provided scripts. 8 | -------------------------------------------------------------------------------- /00-provisioning/access-gke.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # please replace "pa-afa-kubernetes" with your own project on GCP 4 | 5 | gcloud auth login 6 | gcloud config set project pa-afa-kubernetes 7 | gcloud container clusters get-credentials "springio2019" 8 | -------------------------------------------------------------------------------- /00-provisioning/create-gke.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # please replace "pa-afa-kubernetes" with your own project on GCP 4 | 5 | gcloud auth login 6 | gcloud config set project pa-afa-kubernetes 7 | 8 | # create kubernetes cluster only using encryption at rest on volume level 9 | gcloud beta container clusters create "springio2019" \ 10 | --cluster-version=latest \ 11 | --zone "europe-west3-a" \ 12 | --project "pa-afa-kubernetes" 13 | 14 | -------------------------------------------------------------------------------- /00-provisioning/delete-gke.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # please replace "pa-afa-kubernetes" with your own project on GCP 4 | 5 | gcloud auth login 6 | gcloud config set project pa-afa-kubernetes 7 | gcloud beta container clusters delete "springio2019" 8 | 9 | -------------------------------------------------------------------------------- /01-config-and-secrets/README.md: -------------------------------------------------------------------------------- 1 | # Spring Cloud on Kubernetes @ Spring I/O 2019 2 | 3 | ## Demos 4 | 5 | * [Spring Boot on Kubernetes (Pure K8s Standard)](hello-spring-kubernetes) 6 | * [Spring Boot on Kubernetes (with Spring Cloud Kubernetes)](hello-spring-cloud-kubernetes) 7 | 8 | Please make sure you first create the corresponding 9 | [K8s service account](hello-spring-cloud-kubernetes/k8s/serviceaccount.sh) before 10 | [deploying](hello-spring-cloud-kubernetes/k8s/deploy.sh) the hello-spring-cloud-kubernetes 11 | app to K8s. 12 | 13 | All containers run as NON-ROOT and corresponding 14 | security context is enforced for pod/container in K8s! -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/.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 | /bin/ 22 | 23 | ### NetBeans ### 24 | /nbproject/private/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | 30 | /.vscode/ 31 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/distroless/java:11 2 | #FROM openjdk:11-jre-slim 3 | COPY hello-spring-cloud-kubernetes-1.0.0-SNAPSHOT.jar app.jar 4 | EXPOSE 8080 5 | #RUN addgroup --system --gid 1002 app && adduser --system --uid 1002 --gid 1002 appuser 6 | USER 1002 7 | CMD ["/app.jar"] 8 | #ENTRYPOINT java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar 9 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/README.md: -------------------------------------------------------------------------------- 1 | # Hello Spring Kubernetes 2 | 3 | This is a demo for using [spring cloud kubernetes](https://spring.io/projects/spring-cloud-kubernetes) to map [Kubernetes](https://kubernetes.io) ConfigMaps and Secrets to Spring property sources. 4 | 5 | ## Demo application 6 | 7 | It contains a simple demo spring boot web application that prints out a hello world message that can be configured via a [Kubernetes ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/). The ConfigMap is read using the Kubernetes client API. 8 | 9 | The config map defines these properties: 10 | 11 | * hello.message=k8s 12 | * hello.prefix=Hi 13 | 14 | As every web application must be secure all endpoints require authentication. 15 | The user credentials are configured using [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) mounted as volumes. This is recommended in favour of environment variables as these can easily be leaked into logs or via ```kubectl describe pod``` command. 16 | 17 | You may read the secrets using the Kubernetes client API as well. But this is not recommended as you have to give 18 | your deployed application rights to read all secrets in same namespace which might be a high security risk. 19 | 20 | The user credentials are configured by these properties: 21 | 22 | * user.username 23 | * user.password 24 | * admin.username 25 | * admin.password 26 | 27 | The contained demo spring boot application exposes a simple RESTful API: 28 | 29 | * http://localhost:8080 - GET a configured hello world message 30 | * http://localhost:8080 - POST with a custom message in request body 31 | * http://localhost:8080/actuator - Actuator endpoints for monitoring 32 | 33 | ## Deploy the application 34 | 35 | The corresponding container image for this application is already 36 | published on [docker hub](https://hub.docker.com). 37 | You can pull it from [there](https://hub.docker.com/r/andifalk/hello-spring-kubernetes) using: 38 | 39 | ``` 40 | docker pull andifalk/hello-spring-kubernetes:latest 41 | ``` 42 | 43 | __Note__: 44 | The container image uses a [Distroless Base Image for Java](https://github.com/GoogleContainerTools/distroless). This reduces not only the size of the resulting image but reduces the attack surface by only including the pure minimum that is required to run a Java application in a container. 45 | 46 | To deploy the container image to a Kubernetes cluster you first have to create the corresponding ConfigMap and Secrets: 47 | 48 | ``` 49 | kubectl create -f k8s/configmap.yaml 50 | kubectl create -f k8s/secrets.yaml 51 | ``` 52 | 53 | When using the spring cloud kubernetes project in a Kubernetes cluster with RBAC enabled it requires configuring a service account for the application. Otherwise the application cannot access the ConfigMaps. 54 | The following commands create the required role for read access to Pods, Services, ConfigMaps, Namespaces and Endpoints. A service account is created (is used by the later app deployment manifest). Finally a role binding between the service account and the role for read access is created. 55 | 56 | ``` 57 | kubectl create -f k8s/read-access-role.yaml 58 | kubectl create -f k8s/serviceaccount.yaml 59 | kubectl create -f k8s/rolebinding-read-access.yaml 60 | ``` 61 | 62 | Now we are set up to deploy our spring boot application using: 63 | 64 | ``` 65 | kubectl create -f k8s/deployment.yaml 66 | ``` 67 | 68 | Please note that the deployment also enforces a pod context security policy that prevents root priviliges on the pod for the docker container. 69 | 70 | ``` 71 | ... 72 | securityContext: 73 | allowPrivilegeEscalation: false 74 | runAsNonRoot: true 75 | ... 76 | ``` 77 | 78 | To access the deployed web application you need to expose it as a service or just use port forwarding directly to the Pod like this: 79 | 80 | ``` 81 | kubectl port-forward pod/[Pod name] 8080:8080 82 | ``` 83 | 84 | You have to replace [Pod name] with your concrete Pod name. You can get the name using: 85 | 86 | ``` 87 | kubectl get pods 88 | ``` 89 | 90 | With port forwarding enabled you should be able to access the deployed application using [localhost:8080](http://localhost:8080). 91 | In the login dialog you need to use the credentials as configured in the _secrets.yaml_ manifest. 92 | 93 | user/k8s_user 94 | admin/k8s_admin -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.1.4.RELEASE' 3 | id 'java' 4 | id "com.palantir.docker" version "0.21.0" 5 | id 'com.adarshr.test-logger' version '1.6.0' 6 | } 7 | 8 | apply plugin: 'io.spring.dependency-management' 9 | 10 | group = 'com.example' 11 | version = '1.0.0-SNAPSHOT' 12 | sourceCompatibility = '1.8' 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | ext { 19 | set('springCloudVersion', 'Greenwich.SR1') 20 | } 21 | 22 | docker { 23 | // name 'andifalk/hello-spring-cloud-kubernetes:latest' 24 | name 'eu.gcr.io/pa-afa-kubernetes/hello-spring-cloud-kubernetes:latest' 25 | files bootJar.archiveFile 26 | } 27 | 28 | test { 29 | useJUnitPlatform() 30 | } 31 | 32 | dependencies { 33 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 34 | implementation 'org.springframework.boot:spring-boot-starter-security' 35 | implementation 'org.springframework.boot:spring-boot-starter-web' 36 | implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-config' 37 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 38 | testImplementation 'org.springframework.security:spring-security-test' 39 | testImplementation('org.mockito:mockito-junit-jupiter') 40 | testImplementation('org.junit.jupiter:junit-jupiter-api') 41 | testImplementation('org.junit.jupiter:junit-jupiter-params') 42 | testRuntime('org.junit.jupiter:junit-jupiter-engine') 43 | } 44 | 45 | dependencyManagement { 46 | imports { 47 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/01-config-and-secrets/hello-spring-cloud-kubernetes/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/config_secrets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl apply -f ./configmap-yaml.yaml 4 | kubectl apply -f ./secrets.yaml 5 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/configmap-properties.yaml: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: hello-spring-cloud-kubernetes 5 | namespace: default 6 | data: 7 | application.properties: |- 8 | hello.message=k8s 9 | hello.prefix=Hi 10 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/configmap-yaml.yaml: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: hello-spring-cloud-kubernetes 5 | namespace: default 6 | data: 7 | application.yaml: |- 8 | hello: 9 | message: k8s 10 | prefix: Hi 11 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/configmap.yaml: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: hello-spring-cloud-kubernetes 5 | namespace: default 6 | data: 7 | hello.message: k8s 8 | hello.prefix: Hi 9 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl apply -f ./deployment.yaml 4 | kubectl apply -f ./service.yaml 5 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: hello-spring-cloud-kubernetes 6 | name: hello-spring-cloud-kubernetes 7 | spec: 8 | replicas: 2 9 | selector: 10 | matchLabels: 11 | app: hello-spring-cloud-kubernetes 12 | template: 13 | metadata: 14 | labels: 15 | app: hello-spring-cloud-kubernetes 16 | spec: 17 | serviceAccountName: hello-spring-cloud-kubernetes 18 | securityContext: 19 | runAsNonRoot: true 20 | containers: 21 | - image: eu.gcr.io/pa-afa-kubernetes/hello-spring-cloud-kubernetes:latest 22 | name: hello-spring-cloud-kubernetes 23 | imagePullPolicy: Always 24 | env: 25 | - name: KUBERNETES_NAMESPACE 26 | value: default 27 | resources: 28 | limits: 29 | memory: "768Mi" 30 | securityContext: 31 | allowPrivilegeEscalation: false 32 | privileged: false 33 | runAsNonRoot: true 34 | capabilities: 35 | drop: 36 | - ALL 37 | livenessProbe: 38 | httpGet: 39 | path: /actuator/health 40 | port: 8080 41 | initialDelaySeconds: 5 42 | periodSeconds: 5 43 | volumeMounts: 44 | - name: "hello-spring-cloud-kubernetes" 45 | readOnly: true 46 | mountPath: "/etc/secrets/hello-spring-cloud-kubernetes" 47 | volumes: 48 | - name: "hello-spring-cloud-kubernetes" 49 | secret: 50 | secretName: "hello-spring-cloud-kubernetes" 51 | restartPolicy: Always 52 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/read-access-role.yaml: -------------------------------------------------------------------------------- 1 | kind: Role 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | namespace: default 5 | name: hello-spring-cloud-kubernetes-read 6 | rules: 7 | - apiGroups: [""] 8 | resources: ["namespaces", "pods", "services", "endpoints", "configmaps"] 9 | verbs: ["get", "list", "watch"] -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/rolebinding-read-access.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: hello-spring-cloud-kubernetes-read 5 | namespace: default 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: Role 9 | name: hello-spring-cloud-kubernetes-read 10 | subjects: 11 | - kind: ServiceAccount 12 | name: hello-spring-cloud-kubernetes 13 | namespace: default -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: hello-spring-cloud-kubernetes 5 | namespace: default 6 | type: Opaque 7 | data: 8 | user.username: dXNlcg== 9 | user.password: azhzX3VzZXI= 10 | admin.username: YWRtaW4= 11 | admin.password: azhzX2FkbWlu -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: hello-spring-cloud-kubernetes 5 | spec: 6 | selector: 7 | app: hello-spring-cloud-kubernetes 8 | ports: 9 | - port: 8081 10 | targetPort: 8080 11 | type: LoadBalancer -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/serviceaccount.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl apply -f ./read-access-role.yaml 4 | kubectl apply -f ./serviceaccount.yaml 5 | kubectl apply -f ./rolebinding-read-access.yaml 6 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/k8s/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: hello-spring-cloud-kubernetes 5 | namespace: default -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | rootProject.name = 'hello-spring-cloud-kubernetes' 7 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/src/main/java/com/example/hello/HelloController.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.cloud.context.config.annotation.RefreshScope; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.ResponseStatus; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import static org.springframework.http.HttpStatus.CREATED; 15 | import static org.springframework.http.HttpStatus.OK; 16 | 17 | @RestController 18 | @RefreshScope 19 | @RequestMapping("/") 20 | public class HelloController { 21 | 22 | private static final Logger LOGGER = LoggerFactory.getLogger(HelloController.class); 23 | 24 | private final String defaultMessage; 25 | 26 | private final String defaultPrefix; 27 | 28 | public HelloController(@Autowired MessageConfig messageConfig) { 29 | this.defaultPrefix = messageConfig.getPrefix(); 30 | this.defaultMessage = messageConfig.getMessage(); 31 | LOGGER.info("prefix '{}'", defaultPrefix); 32 | LOGGER.info("message '{}'", defaultMessage); 33 | } 34 | 35 | @ResponseStatus(OK) 36 | @GetMapping 37 | public Message sayHello() { 38 | return new Message(String.format("%s %s", defaultPrefix, defaultMessage)); 39 | } 40 | 41 | @ResponseStatus(CREATED) 42 | @PostMapping 43 | public Message sayHelloInBody(@RequestBody String message) { 44 | return new Message(defaultPrefix + " " + (message != null ? message : defaultMessage)); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/src/main/java/com/example/hello/HelloSpringCloudKubernetesApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 6 | 7 | @SpringBootApplication 8 | @EnableConfigurationProperties(MessageConfig.class) 9 | public class HelloSpringCloudKubernetesApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(HelloSpringCloudKubernetesApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/src/main/java/com/example/hello/HelloUserService.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.security.core.userdetails.User; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 15 | 16 | @Configuration 17 | public class HelloUserService { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(HelloUserService.class); 20 | 21 | @Value("${user.username:user}") 22 | private String userUsername; 23 | 24 | @Value("${user.password:secret}") 25 | private String userPassword; 26 | 27 | @Value("${admin.username:admin}") 28 | private String adminUsername; 29 | 30 | @Value("${admin.password:secret}") 31 | private String adminPassword; 32 | 33 | @Bean 34 | PasswordEncoder passwordEncoder() { 35 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 36 | } 37 | 38 | @Bean 39 | UserDetailsService helloUserDetailsService() { 40 | LOGGER.info("Password for user '{}': '{}'", userUsername, userPassword); 41 | LOGGER.info("Password for user '{}': '{}'", adminUsername, adminPassword); 42 | return new InMemoryUserDetailsManager( 43 | Arrays.asList( 44 | User.withUsername(userUsername).password(passwordEncoder().encode(userPassword)).roles("USER").build(), 45 | User.withUsername(adminUsername).password(passwordEncoder().encode(adminPassword)).roles("USER", "ADMIN").build() 46 | ) 47 | ); 48 | } 49 | } -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/src/main/java/com/example/hello/Message.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | public class Message { 4 | private final String message; 5 | 6 | public Message(String message) { 7 | this.message = message; 8 | } 9 | 10 | public String getMessage() { 11 | return message; 12 | } 13 | } -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/src/main/java/com/example/hello/MessageConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ConfigurationProperties(prefix = "hello") 8 | public class MessageConfig { 9 | private String message = "World"; 10 | private String prefix = "Hello"; 11 | 12 | public String getMessage() { 13 | return message; 14 | } 15 | 16 | public String getPrefix() { 17 | return prefix; 18 | } 19 | 20 | public void setMessage(String message) { 21 | this.message = message; 22 | } 23 | 24 | public void setPrefix(String prefix) { 25 | this.prefix = prefix; 26 | } 27 | } -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | management: 2 | endpoint: 3 | restart: 4 | enabled: true 5 | health: 6 | show-details: always 7 | endpoints: 8 | web: 9 | exposure: 10 | include: '*' 11 | 12 | logging: 13 | level: 14 | org: 15 | springframework: 16 | cloud: 17 | kubernetes: debug 18 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/src/main/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: hello-spring-cloud-kubernetes 4 | cloud: 5 | kubernetes: 6 | # automatic reloading of config props 7 | reload: 8 | enabled: true 9 | mode: polling 10 | period: 1000 11 | strategy: refresh 12 | # kubernetes config maps 13 | config: 14 | enabled: true 15 | enable-api: true 16 | namespace: default 17 | sources: 18 | - name: ${spring.application.name} 19 | # kubernetes secrets 20 | secrets: 21 | enabled: true 22 | # Not recommended to use api for reading secrets 23 | # See https://kubernetes.io/docs/concepts/configuration/secret/#best-practices: 24 | # "For these reasons watch and list requests for secrets within a namespace are extremely powerful 25 | # capabilities and should be avoided, since listing secrets allows the clients to inspect 26 | # the values of all secrets that are in that namespace." 27 | # Only use this for local dev purposes (to see if secrets are generally found and can be mapped) 28 | enable-api: false 29 | namespace: default 30 | name: ${spring.application.name} 31 | paths: 32 | - /etc/secrets/${spring.application.name} 33 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-cloud-kubernetes/src/test/java/com/example/hello/HelloControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.security.test.context.support.WithMockUser; 9 | import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; 10 | import org.springframework.test.context.junit.jupiter.SpringExtension; 11 | import org.springframework.test.web.servlet.MockMvc; 12 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 13 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 14 | 15 | import static org.hamcrest.Matchers.containsString; 16 | 17 | @ExtendWith(SpringExtension.class) 18 | @SpringBootTest 19 | @AutoConfigureMockMvc 20 | @WithMockUser 21 | class HelloControllerTests { 22 | 23 | @Autowired 24 | private MockMvc mockMvc; 25 | 26 | @Autowired 27 | private MessageConfig messageConfig; 28 | 29 | @Test 30 | void verifyGetHello() throws Exception { 31 | mockMvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isOk()); 32 | } 33 | 34 | @Test 35 | void verifyHealth() throws Exception { 36 | mockMvc.perform(MockMvcRequestBuilders.get("/actuator/health")) 37 | .andExpect(MockMvcResultMatchers.status().isOk()) 38 | .andExpect(MockMvcResultMatchers.content().string(containsString("UP"))); 39 | } 40 | 41 | @Test 42 | void verifyPostHello() throws Exception { 43 | mockMvc.perform(MockMvcRequestBuilders.post("/").with(SecurityMockMvcRequestPostProcessors.csrf()) 44 | .content("test")).andExpect(MockMvcResultMatchers.status().isCreated()); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/.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 | /bin/ 22 | 23 | ### NetBeans ### 24 | /nbproject/private/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | 30 | /.vscode/ 31 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/distroless/java:11 2 | #FROM openjdk:11-jre-slim 3 | COPY hello-spring-kubernetes-1.0.0-SNAPSHOT.jar app.jar 4 | EXPOSE 8080 5 | #RUN addgroup --system --gid 1002 app && adduser --system --uid 1002 --gid 1002 appuser 6 | USER 1002 7 | CMD ["/app.jar"] 8 | #ENTRYPOINT java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar 9 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/README.md: -------------------------------------------------------------------------------- 1 | # Hello Spring Kubernetes 2 | 3 | This is a demo for using [spring cloud kubernetes](https://spring.io/projects/spring-cloud-kubernetes) to map [Kubernetes](https://kubernetes.io) ConfigMaps and Secrets to Spring property sources. 4 | 5 | ## Demo application 6 | 7 | It contains a simple demo spring boot web application that prints out a hello world message that can be configured via a [Kubernetes ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/). The ConfigMap is read using the Kubernetes client API. 8 | 9 | The config map defines these properties: 10 | 11 | * hello.message=k8s 12 | * hello.prefix=Hi 13 | 14 | As every web application must be secure all endpoints require authentication. 15 | The user credentials are configured using [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) mounted as volumes. This is recommended in favour of environment variables as these can easily be leaked into logs or via ```kubectl describe pod``` command. 16 | 17 | You may read the secrets using the Kubernetes client API as well. But this is not recommended as you have to give 18 | your deployed application rights to read all secrets in same namespace which might be a high security risk. 19 | 20 | The user credentials are configured by these properties: 21 | 22 | * user.username 23 | * user.password 24 | * admin.username 25 | * admin.password 26 | 27 | The contained demo spring boot application exposes a simple RESTful API: 28 | 29 | * http://localhost:8080 - GET a configured hello world message 30 | * http://localhost:8080 - POST with a custom message in request body 31 | * http://localhost:8080/actuator - Actuator endpoints for monitoring 32 | 33 | ## Deploy the application 34 | 35 | The corresponding container image for this application is already 36 | published on [docker hub](https://hub.docker.com). 37 | You can pull it from [there](https://hub.docker.com/r/andifalk/hello-spring-kubernetes) using: 38 | 39 | ``` 40 | docker pull andifalk/hello-spring-kubernetes:latest 41 | ``` 42 | 43 | __Note__: 44 | The container image uses a [Distroless Base Image for Java](https://github.com/GoogleContainerTools/distroless). This reduces not only the size of the resulting image but reduces the attack surface by only including the pure minimum that is required to run a Java application in a container. 45 | 46 | To deploy the container image to a Kubernetes cluster you first have to create the corresponding ConfigMap and Secrets: 47 | 48 | ``` 49 | kubectl create -f k8s/configmap.yaml 50 | kubectl create -f k8s/secrets.yaml 51 | ``` 52 | 53 | When using the spring cloud kubernetes project in a Kubernetes cluster with RBAC enabled it requires configuring a service account for the application. Otherwise the application cannot access the ConfigMaps. 54 | The following commands create the required role for read access to Pods, Services, ConfigMaps, Namespaces and Endpoints. A service account is created (is used by the later app deployment manifest). Finally a role binding between the service account and the role for read access is created. 55 | 56 | ``` 57 | kubectl create -f k8s/read-access-role.yaml 58 | kubectl create -f k8s/serviceaccount.yaml 59 | kubectl create -f k8s/rolebinding-read-access.yaml 60 | ``` 61 | 62 | Now we are set up to deploy our spring boot application using: 63 | 64 | ``` 65 | kubectl create -f k8s/deployment.yaml 66 | ``` 67 | 68 | Please note that the deployment also enforces a pod context security policy that prevents root priviliges on the pod for the docker container. 69 | 70 | ``` 71 | ... 72 | securityContext: 73 | allowPrivilegeEscalation: false 74 | runAsNonRoot: true 75 | ... 76 | ``` 77 | 78 | To access the deployed web application you need to expose it as a service or just use port forwarding directly to the Pod like this: 79 | 80 | ``` 81 | kubectl port-forward pod/[Pod name] 8080:8080 82 | ``` 83 | 84 | You have to replace [Pod name] with your concrete Pod name. You can get the name using: 85 | 86 | ``` 87 | kubectl get pods 88 | ``` 89 | 90 | With port forwarding enabled you should be able to access the deployed application using [localhost:8080](http://localhost:8080). 91 | In the login dialog you need to use the credentials as configured in the _secrets.yaml_ manifest. 92 | 93 | user/k8s_user 94 | admin/k8s_admin -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.1.4.RELEASE' 3 | id 'java' 4 | id "com.palantir.docker" version "0.21.0" 5 | id 'com.adarshr.test-logger' version '1.6.0' 6 | } 7 | 8 | apply plugin: 'io.spring.dependency-management' 9 | 10 | group = 'com.example' 11 | version = '1.0.0-SNAPSHOT' 12 | sourceCompatibility = '1.8' 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | docker { 19 | // name 'andifalk/hello-spring-kubernetes:latest' 20 | name 'eu.gcr.io/pa-afa-kubernetes/hello-spring-kubernetes:latest' 21 | files bootJar.archiveFile 22 | } 23 | 24 | test { 25 | useJUnitPlatform() 26 | } 27 | 28 | dependencies { 29 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 30 | implementation 'org.springframework.boot:spring-boot-starter-security' 31 | implementation 'org.springframework.boot:spring-boot-starter-web' 32 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 33 | testImplementation 'org.springframework.security:spring-security-test' 34 | testImplementation('org.mockito:mockito-junit-jupiter') 35 | testImplementation('org.junit.jupiter:junit-jupiter-api') 36 | testImplementation('org.junit.jupiter:junit-jupiter-params') 37 | testRuntime('org.junit.jupiter:junit-jupiter-engine') 38 | } 39 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/01-config-and-secrets/hello-spring-kubernetes/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/k8s/config_secrets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl apply -f ./configmap.yaml 4 | kubectl apply -f ./secrets.yaml 5 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/k8s/configmap.yaml: -------------------------------------------------------------------------------- 1 | kind: ConfigMap 2 | apiVersion: v1 3 | metadata: 4 | name: hello-spring-kubernetes 5 | namespace: default 6 | data: 7 | hello.message: k8s 8 | hello.prefix: Hi 9 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/k8s/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl apply -f ./deployment.yaml 4 | kubectl apply -f ./service.yaml 5 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: hello-spring-kubernetes 6 | name: hello-spring-kubernetes 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: hello-spring-kubernetes 12 | template: 13 | metadata: 14 | labels: 15 | app: hello-spring-kubernetes 16 | spec: 17 | securityContext: 18 | runAsNonRoot: true 19 | containers: 20 | - image: eu.gcr.io/pa-afa-kubernetes/hello-spring-kubernetes:latest 21 | name: hello-spring-kubernetes 22 | imagePullPolicy: Always 23 | env: 24 | - name: HELLO_MESSAGE 25 | valueFrom: 26 | configMapKeyRef: 27 | name: hello-spring-kubernetes 28 | key: hello.message 29 | - name: HELLO_PREFIX 30 | valueFrom: 31 | configMapKeyRef: 32 | name: hello-spring-kubernetes 33 | key: hello.prefix 34 | - name: USER_USERNAME 35 | valueFrom: 36 | secretKeyRef: 37 | name: hello-spring-kubernetes 38 | key: user.username 39 | - name: USER_PASSWORD 40 | valueFrom: 41 | secretKeyRef: 42 | name: hello-spring-kubernetes 43 | key: user.password 44 | - name: ADMIN_USERNAME 45 | valueFrom: 46 | secretKeyRef: 47 | name: hello-spring-kubernetes 48 | key: admin.username 49 | - name: ADMIN_PASSWORD 50 | valueFrom: 51 | secretKeyRef: 52 | name: hello-spring-kubernetes 53 | key: admin.password 54 | resources: 55 | limits: 56 | memory: "768Mi" 57 | securityContext: 58 | allowPrivilegeEscalation: false 59 | privileged: false 60 | runAsNonRoot: true 61 | capabilities: 62 | drop: 63 | - ALL 64 | livenessProbe: 65 | httpGet: 66 | path: /actuator/health 67 | port: 8080 68 | initialDelaySeconds: 5 69 | periodSeconds: 5 70 | restartPolicy: Always 71 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/k8s/secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: hello-spring-kubernetes 5 | namespace: default 6 | type: Opaque 7 | data: 8 | user.username: dXNlcg== 9 | user.password: azhzX3VzZXI= 10 | admin.username: YWRtaW4= 11 | admin.password: azhzX2FkbWlu -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: hello-spring-kubernetes 5 | spec: 6 | selector: 7 | app: hello-spring-kubernetes 8 | ports: 9 | - port: 8081 10 | targetPort: 8080 11 | type: LoadBalancer -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | rootProject.name = 'hello-spring-kubernetes' 7 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/src/main/java/com/example/hello/HelloController.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.ResponseStatus; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import static org.springframework.http.HttpStatus.CREATED; 14 | import static org.springframework.http.HttpStatus.OK; 15 | 16 | @RestController 17 | @RequestMapping("/") 18 | public class HelloController { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(HelloController.class); 21 | 22 | private final String defaultMessage; 23 | 24 | private final String defaultPrefix; 25 | 26 | public HelloController(@Autowired MessageConfig messageConfig) { 27 | this.defaultPrefix = messageConfig.getPrefix(); 28 | this.defaultMessage = messageConfig.getMessage(); 29 | LOGGER.info("prefix '{}'", defaultPrefix); 30 | LOGGER.info("message '{}'", defaultMessage); 31 | } 32 | 33 | @ResponseStatus(OK) 34 | @GetMapping 35 | public Message sayHello() { 36 | return new Message(String.format("%s %s", defaultPrefix, defaultMessage)); 37 | } 38 | 39 | @ResponseStatus(CREATED) 40 | @PostMapping 41 | public Message sayHelloInBody(@RequestBody String message) { 42 | return new Message(defaultPrefix + " " + (message != null ? message : defaultMessage)); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/src/main/java/com/example/hello/HelloSpringKubernetesApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class HelloSpringKubernetesApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(HelloSpringKubernetesApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/src/main/java/com/example/hello/HelloUserService.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.security.core.userdetails.User; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 15 | 16 | @Configuration 17 | public class HelloUserService { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(HelloUserService.class); 20 | 21 | @Value("${user.username:user}") 22 | private String userUsername; 23 | 24 | @Value("${user.password:secret}") 25 | private String userPassword; 26 | 27 | @Value("${admin.username:admin}") 28 | private String adminUsername; 29 | 30 | @Value("${admin.password:secret}") 31 | private String adminPassword; 32 | 33 | @Bean 34 | PasswordEncoder passwordEncoder() { 35 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 36 | } 37 | 38 | @Bean 39 | UserDetailsService helloUserDetailsService() { 40 | LOGGER.info("Password for user '{}': '{}'", userUsername.trim(), userPassword.trim()); 41 | LOGGER.info("Password for user '{}': '{}'", adminUsername, adminPassword); 42 | return new InMemoryUserDetailsManager( 43 | Arrays.asList( 44 | User.withUsername(userUsername).password(passwordEncoder().encode(userPassword)).roles("USER").build(), 45 | User.withUsername(adminUsername).password(passwordEncoder().encode(adminPassword)).roles("USER", "ADMIN").build() 46 | ) 47 | ); 48 | } 49 | } -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/src/main/java/com/example/hello/Message.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | public class Message { 4 | private final String message; 5 | 6 | public Message(String message) { 7 | this.message = message; 8 | } 9 | 10 | public String getMessage() { 11 | return message; 12 | } 13 | } -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/src/main/java/com/example/hello/MessageConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ConfigurationProperties(prefix = "hello") 8 | public class MessageConfig { 9 | private String message = "World"; 10 | private String prefix = "Hello"; 11 | 12 | public String getMessage() { 13 | return message; 14 | } 15 | 16 | public String getPrefix() { 17 | return prefix; 18 | } 19 | 20 | public void setMessage(String message) { 21 | this.message = message; 22 | } 23 | 24 | public void setPrefix(String prefix) { 25 | this.prefix = prefix; 26 | } 27 | } -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: hello-spring-kubernetes 4 | 5 | management: 6 | endpoint: 7 | health: 8 | show-details: always 9 | endpoints: 10 | web: 11 | exposure: 12 | include: '*' 13 | -------------------------------------------------------------------------------- /01-config-and-secrets/hello-spring-kubernetes/src/test/java/com/example/hello/HelloControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.example.hello; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.security.test.context.support.WithMockUser; 9 | import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; 10 | import org.springframework.test.context.junit.jupiter.SpringExtension; 11 | import org.springframework.test.web.servlet.MockMvc; 12 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 13 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 14 | 15 | import static org.hamcrest.Matchers.containsString; 16 | 17 | @ExtendWith(SpringExtension.class) 18 | @SpringBootTest 19 | @AutoConfigureMockMvc 20 | @WithMockUser 21 | class HelloControllerTests { 22 | 23 | @Autowired 24 | private MockMvc mockMvc; 25 | 26 | @Autowired 27 | private MessageConfig messageConfig; 28 | 29 | @Test 30 | void verifyGetHello() throws Exception { 31 | mockMvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isOk()); 32 | } 33 | 34 | @Test 35 | void verifyHealth() throws Exception { 36 | mockMvc.perform(MockMvcRequestBuilders.get("/actuator/health")) 37 | .andExpect(MockMvcResultMatchers.status().isOk()) 38 | .andExpect(MockMvcResultMatchers.content().string(containsString("UP"))); 39 | } 40 | 41 | @Test 42 | void verifyPostHello() throws Exception { 43 | mockMvc.perform(MockMvcRequestBuilders.post("/").with(SecurityMockMvcRequestPostProcessors.csrf()) 44 | .content("test")).andExpect(MockMvcResultMatchers.status().isCreated()); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /02-discovery/README.md: -------------------------------------------------------------------------------- 1 | # Spring Cloud on Kubernetes @ Spring I/O 2019 2 | 3 | ## Demos 4 | 5 | * [Spring Boot discovery client (with Spring Cloud Kubernetes)](question-client) 6 | * [Spring Boot discovered service](answering-service) 7 | 8 | Please make sure you first create the corresponding 9 | [K8s service account](question-client/k8s/serviceaccount.sh) before 10 | [deploying](question-client/k8s/deploy.sh) the discovery client to K8s. 11 | 12 | All containers run as NON-ROOT and corresponding 13 | security context is enforced for pod/container in K8s! 14 | 15 | -------------------------------------------------------------------------------- /02-discovery/answering-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 | -------------------------------------------------------------------------------- /02-discovery/answering-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/distroless/java:11 2 | COPY answering-service-1.0.0-SNAPSHOT.jar app.jar 3 | EXPOSE 9090 4 | USER 1002 5 | CMD ["/app.jar"] 6 | -------------------------------------------------------------------------------- /02-discovery/answering-service/bin/main/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /02-discovery/answering-service/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.1.4.RELEASE' 3 | id 'java' 4 | id "com.palantir.docker" version "0.21.0" 5 | id 'com.adarshr.test-logger' version '1.6.0' 6 | } 7 | 8 | apply plugin: 'io.spring.dependency-management' 9 | 10 | group = 'com.example' 11 | version = '1.0.0-SNAPSHOT' 12 | sourceCompatibility = '1.8' 13 | 14 | ext { 15 | set('springCloudVersion', 'Greenwich.SR1') 16 | } 17 | 18 | docker { 19 | // name 'andifalk/discovery1:latest' 20 | name 'eu.gcr.io/pa-afa-kubernetes/answering-service:latest' 21 | files bootJar.archiveFile 22 | } 23 | 24 | test { 25 | useJUnitPlatform() 26 | } 27 | 28 | repositories { 29 | mavenCentral() 30 | } 31 | 32 | dependencies { 33 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 34 | implementation 'org.springframework.boot:spring-boot-starter-webflux' 35 | implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes' 36 | runtimeOnly 'org.springframework.boot:spring-boot-devtools' 37 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 38 | testImplementation 'org.mockito:mockito-junit-jupiter' 39 | testImplementation 'org.junit.jupiter:junit-jupiter-api' 40 | testImplementation 'org.junit.jupiter:junit-jupiter-params' 41 | testRuntime 'org.junit.jupiter:junit-jupiter-engine' 42 | } 43 | 44 | dependencyManagement { 45 | imports { 46 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /02-discovery/answering-service/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/02-discovery/answering-service/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /02-discovery/answering-service/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /02-discovery/answering-service/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /02-discovery/answering-service/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /02-discovery/answering-service/k8s/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl apply -f ./deployment.yaml 4 | kubectl apply -f ./service.yaml 5 | -------------------------------------------------------------------------------- /02-discovery/answering-service/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: answering-service 6 | name: answering-service 7 | spec: 8 | replicas: 2 9 | selector: 10 | matchLabels: 11 | app: answering-service 12 | template: 13 | metadata: 14 | labels: 15 | app: answering-service 16 | spec: 17 | securityContext: 18 | runAsNonRoot: true 19 | containers: 20 | - image: eu.gcr.io/pa-afa-kubernetes/answering-service:latest 21 | name: answering-service 22 | imagePullPolicy: Always 23 | resources: 24 | limits: 25 | memory: "768Mi" 26 | securityContext: 27 | allowPrivilegeEscalation: false 28 | privileged: false 29 | runAsNonRoot: true 30 | capabilities: 31 | drop: 32 | - ALL 33 | livenessProbe: 34 | httpGet: 35 | path: /actuator/health 36 | port: 9090 37 | initialDelaySeconds: 5 38 | periodSeconds: 5 39 | restartPolicy: Always 40 | -------------------------------------------------------------------------------- /02-discovery/answering-service/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: answering-service 5 | spec: 6 | selector: 7 | app: answering-service 8 | ports: 9 | - port: 8081 10 | targetPort: 9090 11 | type: LoadBalancer -------------------------------------------------------------------------------- /02-discovery/answering-service/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | rootProject.name = 'answering-service' 7 | -------------------------------------------------------------------------------- /02-discovery/answering-service/src/main/java/com/example/discovery/server/AnsweringRestController.java: -------------------------------------------------------------------------------- 1 | package com.example.discovery.server; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | import reactor.core.publisher.Mono; 6 | 7 | @RestController 8 | public class AnsweringRestController { 9 | 10 | @GetMapping("/") 11 | Mono questionAndAnswer() { 12 | return Mono.just("42"); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /02-discovery/answering-service/src/main/java/com/example/discovery/server/AnsweringServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.discovery.server; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class AnsweringServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(AnsweringServiceApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /02-discovery/answering-service/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | management: 2 | endpoint: 3 | health: 4 | show-details: always 5 | endpoints: 6 | web: 7 | exposure: 8 | include: '*' 9 | 10 | spring: 11 | cloud: 12 | discovery: 13 | enabled: false 14 | 15 | 16 | logging: 17 | level: 18 | org: 19 | springframework: 20 | cloud: 21 | kubernetes: debug 22 | server: 23 | port: 9090 -------------------------------------------------------------------------------- /02-discovery/answering-service/src/main/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: answering-service 4 | 5 | -------------------------------------------------------------------------------- /02-discovery/answering-service/src/test/java/com/example/discovery/server/AnsweringServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.discovery.server; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit.jupiter.SpringExtension; 7 | 8 | @ExtendWith(SpringExtension.class) 9 | @SpringBootTest 10 | class AnsweringServiceApplicationTests { 11 | 12 | @Test 13 | void contextLoads() {} 14 | } 15 | -------------------------------------------------------------------------------- /02-discovery/question-client/.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 | -------------------------------------------------------------------------------- /02-discovery/question-client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcr.io/distroless/java:11 2 | COPY question-client-1.0.0-SNAPSHOT.jar app.jar 3 | EXPOSE 8080 4 | USER 1002 5 | CMD ["/app.jar"] 6 | -------------------------------------------------------------------------------- /02-discovery/question-client/bin/main/application.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /02-discovery/question-client/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.1.4.RELEASE' 3 | id 'java' 4 | id "com.palantir.docker" version "0.21.0" 5 | id 'com.adarshr.test-logger' version '1.6.0' 6 | } 7 | 8 | apply plugin: 'io.spring.dependency-management' 9 | 10 | group = 'com.example' 11 | version = '1.0.0-SNAPSHOT' 12 | sourceCompatibility = '1.8' 13 | 14 | ext { 15 | set('springCloudVersion', 'Greenwich.SR1') 16 | } 17 | 18 | docker { 19 | // name 'andifalk/discovery1:latest' 20 | name 'eu.gcr.io/pa-afa-kubernetes/question-service:latest' 21 | files bootJar.archiveFile 22 | } 23 | 24 | test { 25 | useJUnitPlatform() 26 | } 27 | 28 | repositories { 29 | mavenCentral() 30 | } 31 | 32 | dependencies { 33 | implementation 'org.springframework.boot:spring-boot-starter-actuator' 34 | implementation 'org.springframework.boot:spring-boot-starter-webflux' 35 | implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes' 36 | implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-ribbon' 37 | runtimeOnly 'org.springframework.boot:spring-boot-devtools' 38 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 39 | testImplementation 'org.mockito:mockito-junit-jupiter' 40 | testImplementation 'org.junit.jupiter:junit-jupiter-api' 41 | testImplementation 'org.junit.jupiter:junit-jupiter-params' 42 | testRuntime 'org.junit.jupiter:junit-jupiter-engine' 43 | } 44 | 45 | dependencyManagement { 46 | imports { 47 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /02-discovery/question-client/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/02-discovery/question-client/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /02-discovery/question-client/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /02-discovery/question-client/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /02-discovery/question-client/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /02-discovery/question-client/k8s/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl apply -f ./deployment.yaml 4 | kubectl apply -f ./service.yaml 5 | -------------------------------------------------------------------------------- /02-discovery/question-client/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app: question-service 6 | name: question-service 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: question-service 12 | template: 13 | metadata: 14 | labels: 15 | app: question-service 16 | spec: 17 | serviceAccountName: question-service 18 | securityContext: 19 | runAsNonRoot: true 20 | containers: 21 | - image: eu.gcr.io/pa-afa-kubernetes/question-service:latest 22 | name: question-service 23 | imagePullPolicy: Always 24 | resources: 25 | limits: 26 | memory: "768Mi" 27 | securityContext: 28 | allowPrivilegeEscalation: false 29 | privileged: false 30 | runAsNonRoot: true 31 | capabilities: 32 | drop: 33 | - ALL 34 | livenessProbe: 35 | httpGet: 36 | path: /actuator/health 37 | port: 8080 38 | initialDelaySeconds: 5 39 | periodSeconds: 5 40 | restartPolicy: Always 41 | -------------------------------------------------------------------------------- /02-discovery/question-client/k8s/read-access-role.yaml: -------------------------------------------------------------------------------- 1 | kind: Role 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | namespace: default 5 | name: question-service-read 6 | rules: 7 | - apiGroups: [""] 8 | resources: ["pods", "services", "endpoints"] 9 | verbs: ["get", "list", "watch"] -------------------------------------------------------------------------------- /02-discovery/question-client/k8s/rolebinding-read-access.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: question-service-read 5 | namespace: default 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: Role 9 | name: question-service-read 10 | subjects: 11 | - kind: ServiceAccount 12 | name: question-service 13 | namespace: default -------------------------------------------------------------------------------- /02-discovery/question-client/k8s/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: question-service 5 | spec: 6 | selector: 7 | app: question-service 8 | ports: 9 | - port: 8081 10 | targetPort: 8080 11 | type: LoadBalancer -------------------------------------------------------------------------------- /02-discovery/question-client/k8s/serviceaccount.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | kubectl apply -f ./read-access-role.yaml 4 | kubectl apply -f ./serviceaccount.yaml 5 | kubectl apply -f ./rolebinding-read-access.yaml 6 | -------------------------------------------------------------------------------- /02-discovery/question-client/k8s/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: question-service 5 | namespace: default -------------------------------------------------------------------------------- /02-discovery/question-client/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | rootProject.name = 'question-client' 7 | -------------------------------------------------------------------------------- /02-discovery/question-client/src/main/java/com/example/discovery/client/QuestionClientApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.discovery.client; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | 7 | @EnableDiscoveryClient 8 | @SpringBootApplication 9 | public class QuestionClientApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(QuestionClientApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /02-discovery/question-client/src/main/java/com/example/discovery/client/QuestionRestController.java: -------------------------------------------------------------------------------- 1 | package com.example.discovery.client; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.cloud.client.ServiceInstance; 6 | import org.springframework.cloud.client.discovery.DiscoveryClient; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import org.springframework.web.reactive.function.client.WebClient; 10 | import reactor.core.publisher.Mono; 11 | 12 | import java.net.MalformedURLException; 13 | import java.net.URI; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | @RestController 18 | public class QuestionRestController { 19 | 20 | private static final Logger LOG = LoggerFactory.getLogger(QuestionRestController.class); 21 | 22 | private final DiscoveryClient discoveryClient; 23 | private final WebClient webClient; 24 | 25 | public QuestionRestController(DiscoveryClient discoveryClient, WebClient webClient) { 26 | this.discoveryClient = discoveryClient; 27 | this.webClient = webClient; 28 | } 29 | 30 | @GetMapping("/") 31 | Mono questionAndAnswer() throws MalformedURLException { 32 | String url = null; 33 | List services = this.discoveryClient.getServices(); 34 | 35 | for (String service : services) { 36 | if (!service.equals("answering-service")) { 37 | continue; 38 | } 39 | List instances = this.discoveryClient.getInstances(service); 40 | for (ServiceInstance se : instances) { 41 | Map metadata = se.getMetadata(); 42 | LOG.info("Metadata: {}", metadata); 43 | LOG.info("ServiceId: {}", se.getServiceId()); 44 | LOG.info("URI: {}", se.getUri().toString()); 45 | url = se.getUri().toString(); 46 | } 47 | } 48 | 49 | if (url != null) { 50 | return webClient.get().uri(URI.create(url)) 51 | .retrieve().bodyToMono(String.class) 52 | .map(a -> "What is the answer to the ultimate question of life, " + 53 | "the universe and everything?\n" 54 | + "The answer is '" + a + "'"); 55 | } else { 56 | return Mono.just("What is the answer to the ultimate question of life, " + 57 | "the universe and everything?\n" + "The answer is unknown!!"); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /02-discovery/question-client/src/main/java/com/example/discovery/client/WebClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.discovery.client; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.reactive.function.client.WebClient; 6 | 7 | @Configuration 8 | public class WebClientConfiguration { 9 | 10 | @Bean 11 | WebClient webClient() { 12 | return WebClient.builder().build(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /02-discovery/question-client/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | management: 2 | endpoint: 3 | restart: 4 | enabled: true 5 | health: 6 | show-details: always 7 | endpoints: 8 | web: 9 | exposure: 10 | include: '*' 11 | -------------------------------------------------------------------------------- /02-discovery/question-client/src/main/resources/bootstrap.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: question-client 4 | cloud: 5 | kubernetes: 6 | reload: 7 | enabled: true 8 | 9 | 10 | -------------------------------------------------------------------------------- /02-discovery/question-client/src/test/java/com/example/discovery/client/QuestionClientApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.discovery.client; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit.jupiter.SpringExtension; 7 | 8 | @ExtendWith(SpringExtension.class) 9 | @SpringBootTest 10 | class QuestionClientApplicationTests { 11 | 12 | @Test 13 | void contextLoads() {} 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](https://img.shields.io/badge/License-Apache%20License%202.0-brightgreen.svg)][1] 2 | [![Build Status](https://travis-ci.org/andifalk/kubernetes-spring-io-2019.svg?branch=master)](https://travis-ci.org/andifalk/kubernetes-spring-io-2019) 3 | 4 | # Spring Cloud on Kubernetes @ Spring I/O 2019 5 | 6 | ![Spring IO 2019 Spring Cloud on Kubernetes](docs/images/spring_io_kubernetes_cloud.png) 7 | 8 | Spring Boot and Spring Cloud are great in helping building cloud-native Java or Kotlin applications. 9 | Using Spring Cloud components like Discovery Service, API Gateway or the Config Server have improved 10 | the experience in the cloud to a big extent. 11 | With the rise of Kubernetes, this has changed: Discovery services and configuration/secret management are 12 | provided “out of the box” here. 13 | 14 | In this talk, you will learn how Spring Cloud Kubernetes integrates with Config Maps and Secrets to 15 | provide secure configuration to Spring Boot applications in a seamless manner. 16 | You will also see the interaction of the Spring Cloud DiscoveryClient with the provided 17 | Discovery Services of Kubernetes. 18 | 19 | This presentation will consist of live coding and demos that will use sample applications build with Spring Boot and Spring Cloud Kubernetes. 20 | 21 | This repository contains the complete material for the session at [Spring I/O 2019](https://2019.springio.net/) 22 | on [Spring Cloud on Kubernetes](https://2019.springio.net/sessions/spring-cloud-on-kubernetes) 23 | 24 | ## Presentation slides 25 | 26 | [Presentation slides (Online)](https://andifalk.github.io/kubernetes-spring-io-2019) 27 | 28 | ## Demos 29 | 30 | * [Config Maps and Secrets](01-config-and-secrets/README.md) 31 | * [Discovery](02-discovery/README.md) 32 | 33 | You can run these demos locally on [Minikube](https://github.com/kubernetes/minikube) 34 | or use a public cloud provider like Azure, AWS or GCP. 35 | 36 | In [GCP provisioning](00-provisioning) you find scripts 37 | for setting up & accessing GKE on Google Cloud. 38 | 39 | ## License 40 | 41 | Apache 2.0 licensed 42 | 43 | Copyright (c) by 2019 Andreas Falk 44 | 45 | [1]:http://www.apache.org/licenses/LICENSE-2.0.txt -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | *.iws 4 | *.eml 5 | out/ 6 | .DS_Store 7 | .svn 8 | log/*.log 9 | tmp/** 10 | node_modules/ 11 | .sass-cache 12 | css/reveal.min.css 13 | js/reveal.min.js 14 | package-lock.json 15 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2019 Hakim El Hattab, http://hakim.se, and reveal.js contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Spring I/O 2019 2 | 3 | [Presentation Slides (HTML5)](https://andifalk.github.io/oidc-workshop-spring-io-2019/) -------------------------------------------------------------------------------- /docs/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reveal.js", 3 | "version": "3.8.0", 4 | "main": [ 5 | "js/reveal.js", 6 | "css/reveal.css" 7 | ], 8 | "homepage": "http://revealjs.com", 9 | "license": "MIT", 10 | "description": "The HTML Presentation Framework", 11 | "authors": [ 12 | "Hakim El Hattab " 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/hakimel/reveal.js.git" 17 | }, 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "test" 23 | ] 24 | } -------------------------------------------------------------------------------- /docs/css/print/paper.css: -------------------------------------------------------------------------------- 1 | /* Default Print Stylesheet Template 2 | by Rob Glazebrook of CSSnewbie.com 3 | Last Updated: June 4, 2008 4 | 5 | Feel free (nay, compelled) to edit, append, and 6 | manipulate this file as you see fit. */ 7 | 8 | 9 | @media print { 10 | 11 | /* SECTION 1: Set default width, margin, float, and 12 | background. This prevents elements from extending 13 | beyond the edge of the printed page, and prevents 14 | unnecessary background images from printing */ 15 | html { 16 | background: #fff; 17 | width: auto; 18 | height: auto; 19 | overflow: visible; 20 | } 21 | body { 22 | background: #fff; 23 | font-size: 20pt; 24 | width: auto; 25 | height: auto; 26 | border: 0; 27 | margin: 0 5%; 28 | padding: 0; 29 | overflow: visible; 30 | float: none !important; 31 | } 32 | 33 | /* SECTION 2: Remove any elements not needed in print. 34 | This would include navigation, ads, sidebars, etc. */ 35 | .nestedarrow, 36 | .controls, 37 | .fork-reveal, 38 | .share-reveal, 39 | .state-background, 40 | .reveal .progress, 41 | .reveal .backgrounds, 42 | .reveal .slide-number { 43 | display: none !important; 44 | } 45 | 46 | /* SECTION 3: Set body font face, size, and color. 47 | Consider using a serif font for readability. */ 48 | body, p, td, li, div { 49 | font-size: 20pt!important; 50 | font-family: Georgia, "Times New Roman", Times, serif !important; 51 | color: #000; 52 | } 53 | 54 | /* SECTION 4: Set heading font face, sizes, and color. 55 | Differentiate your headings from your body text. 56 | Perhaps use a large sans-serif for distinction. */ 57 | h1,h2,h3,h4,h5,h6 { 58 | color: #000!important; 59 | height: auto; 60 | line-height: normal; 61 | font-family: Georgia, "Times New Roman", Times, serif !important; 62 | text-shadow: 0 0 0 #000 !important; 63 | text-align: left; 64 | letter-spacing: normal; 65 | } 66 | /* Need to reduce the size of the fonts for printing */ 67 | h1 { font-size: 28pt !important; } 68 | h2 { font-size: 24pt !important; } 69 | h3 { font-size: 22pt !important; } 70 | h4 { font-size: 22pt !important; font-variant: small-caps; } 71 | h5 { font-size: 21pt !important; } 72 | h6 { font-size: 20pt !important; font-style: italic; } 73 | 74 | /* SECTION 5: Make hyperlinks more usable. 75 | Ensure links are underlined, and consider appending 76 | the URL to the end of the link for usability. */ 77 | a:link, 78 | a:visited { 79 | color: #000 !important; 80 | font-weight: bold; 81 | text-decoration: underline; 82 | } 83 | /* 84 | .reveal a:link:after, 85 | .reveal a:visited:after { 86 | content: " (" attr(href) ") "; 87 | color: #222 !important; 88 | font-size: 90%; 89 | } 90 | */ 91 | 92 | 93 | /* SECTION 6: more reveal.js specific additions by @skypanther */ 94 | ul, ol, div, p { 95 | visibility: visible; 96 | position: static; 97 | width: auto; 98 | height: auto; 99 | display: block; 100 | overflow: visible; 101 | margin: 0; 102 | text-align: left !important; 103 | } 104 | .reveal pre, 105 | .reveal table { 106 | margin-left: 0; 107 | margin-right: 0; 108 | } 109 | .reveal pre code { 110 | padding: 20px; 111 | border: 1px solid #ddd; 112 | } 113 | .reveal blockquote { 114 | margin: 20px 0; 115 | } 116 | .reveal .slides { 117 | position: static !important; 118 | width: auto !important; 119 | height: auto !important; 120 | 121 | left: 0 !important; 122 | top: 0 !important; 123 | margin-left: 0 !important; 124 | margin-top: 0 !important; 125 | padding: 0 !important; 126 | zoom: 1 !important; 127 | 128 | overflow: visible !important; 129 | display: block !important; 130 | 131 | text-align: left !important; 132 | -webkit-perspective: none; 133 | -moz-perspective: none; 134 | -ms-perspective: none; 135 | perspective: none; 136 | 137 | -webkit-perspective-origin: 50% 50%; 138 | -moz-perspective-origin: 50% 50%; 139 | -ms-perspective-origin: 50% 50%; 140 | perspective-origin: 50% 50%; 141 | } 142 | .reveal .slides section { 143 | visibility: visible !important; 144 | position: static !important; 145 | width: auto !important; 146 | height: auto !important; 147 | display: block !important; 148 | overflow: visible !important; 149 | 150 | left: 0 !important; 151 | top: 0 !important; 152 | margin-left: 0 !important; 153 | margin-top: 0 !important; 154 | padding: 60px 20px !important; 155 | z-index: auto !important; 156 | 157 | opacity: 1 !important; 158 | 159 | page-break-after: always !important; 160 | 161 | -webkit-transform-style: flat !important; 162 | -moz-transform-style: flat !important; 163 | -ms-transform-style: flat !important; 164 | transform-style: flat !important; 165 | 166 | -webkit-transform: none !important; 167 | -moz-transform: none !important; 168 | -ms-transform: none !important; 169 | transform: none !important; 170 | 171 | -webkit-transition: none !important; 172 | -moz-transition: none !important; 173 | -ms-transition: none !important; 174 | transition: none !important; 175 | } 176 | .reveal .slides section.stack { 177 | padding: 0 !important; 178 | } 179 | .reveal section:last-of-type { 180 | page-break-after: avoid !important; 181 | } 182 | .reveal section .fragment { 183 | opacity: 1 !important; 184 | visibility: visible !important; 185 | 186 | -webkit-transform: none !important; 187 | -moz-transform: none !important; 188 | -ms-transform: none !important; 189 | transform: none !important; 190 | } 191 | .reveal section img { 192 | display: block; 193 | margin: 15px 0px; 194 | background: rgba(255,255,255,1); 195 | border: 1px solid #666; 196 | box-shadow: none; 197 | } 198 | 199 | .reveal section small { 200 | font-size: 0.8em; 201 | } 202 | 203 | } 204 | -------------------------------------------------------------------------------- /docs/css/print/pdf.css: -------------------------------------------------------------------------------- 1 | /** 2 | * This stylesheet is used to print reveal.js 3 | * presentations to PDF. 4 | * 5 | * https://github.com/hakimel/reveal.js#pdf-export 6 | */ 7 | 8 | * { 9 | -webkit-print-color-adjust: exact; 10 | } 11 | 12 | body { 13 | margin: 0 auto !important; 14 | border: 0; 15 | padding: 0; 16 | float: none !important; 17 | overflow: visible; 18 | } 19 | 20 | html { 21 | width: 100%; 22 | height: 100%; 23 | overflow: visible; 24 | } 25 | 26 | /* Remove any elements not needed in print. */ 27 | .nestedarrow, 28 | .reveal .controls, 29 | .reveal .progress, 30 | .reveal .playback, 31 | .reveal.overview, 32 | .fork-reveal, 33 | .share-reveal, 34 | .state-background { 35 | display: none !important; 36 | } 37 | 38 | h1, h2, h3, h4, h5, h6 { 39 | text-shadow: 0 0 0 #000 !important; 40 | } 41 | 42 | .reveal pre code { 43 | overflow: hidden !important; 44 | font-family: Courier, 'Courier New', monospace !important; 45 | } 46 | 47 | ul, ol, div, p { 48 | visibility: visible; 49 | position: static; 50 | width: auto; 51 | height: auto; 52 | display: block; 53 | overflow: visible; 54 | margin: auto; 55 | } 56 | .reveal { 57 | width: auto !important; 58 | height: auto !important; 59 | overflow: hidden !important; 60 | } 61 | .reveal .slides { 62 | position: static; 63 | width: 100% !important; 64 | height: auto !important; 65 | zoom: 1 !important; 66 | 67 | left: auto; 68 | top: auto; 69 | margin: 0 !important; 70 | padding: 0 !important; 71 | 72 | overflow: visible; 73 | display: block; 74 | 75 | perspective: none; 76 | perspective-origin: 50% 50%; 77 | } 78 | 79 | .reveal .slides .pdf-page { 80 | position: relative; 81 | overflow: hidden; 82 | z-index: 1; 83 | 84 | page-break-after: always; 85 | } 86 | 87 | .reveal .slides section { 88 | visibility: visible !important; 89 | display: block !important; 90 | position: absolute !important; 91 | 92 | margin: 0 !important; 93 | padding: 0 !important; 94 | box-sizing: border-box !important; 95 | min-height: 1px; 96 | 97 | opacity: 1 !important; 98 | 99 | transform-style: flat !important; 100 | transform: none !important; 101 | } 102 | 103 | .reveal section.stack { 104 | position: relative !important; 105 | margin: 0 !important; 106 | padding: 0 !important; 107 | page-break-after: avoid !important; 108 | height: auto !important; 109 | min-height: auto !important; 110 | } 111 | 112 | .reveal img { 113 | box-shadow: none; 114 | } 115 | 116 | .reveal .roll { 117 | overflow: visible; 118 | line-height: 1em; 119 | } 120 | 121 | /* Slide backgrounds are placed inside of their slide when exporting to PDF */ 122 | .reveal .slide-background { 123 | display: block !important; 124 | position: absolute; 125 | top: 0; 126 | left: 0; 127 | width: 100%; 128 | height: 100%; 129 | z-index: auto !important; 130 | } 131 | 132 | /* Display slide speaker notes when 'showNotes' is enabled */ 133 | .reveal.show-notes { 134 | max-width: none; 135 | max-height: none; 136 | } 137 | .reveal .speaker-notes-pdf { 138 | display: block; 139 | width: 100%; 140 | height: auto; 141 | max-height: none; 142 | top: auto; 143 | right: auto; 144 | bottom: auto; 145 | left: auto; 146 | z-index: 100; 147 | } 148 | 149 | /* Layout option which makes notes appear on a separate page */ 150 | .reveal .speaker-notes-pdf[data-layout="separate-page"] { 151 | position: relative; 152 | color: inherit; 153 | background-color: transparent; 154 | padding: 20px; 155 | page-break-after: always; 156 | border: 0; 157 | } 158 | 159 | /* Display slide numbers when 'slideNumber' is enabled */ 160 | .reveal .slide-number-pdf { 161 | display: block; 162 | position: absolute; 163 | font-size: 14px; 164 | } 165 | -------------------------------------------------------------------------------- /docs/css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v4.0 | 20180602 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | main, menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, main, menu, nav, section { 29 | display: block; 30 | } -------------------------------------------------------------------------------- /docs/css/theme/README.md: -------------------------------------------------------------------------------- 1 | ## Dependencies 2 | 3 | Themes are written using Sass to keep things modular and reduce the need for repeated selectors across files. Make sure that you have the reveal.js development environment including the Grunt dependencies installed before proceeding: https://github.com/hakimel/reveal.js#full-setup 4 | 5 | ## Creating a Theme 6 | 7 | To create your own theme, start by duplicating a ```.scss``` file in [/css/theme/source](https://github.com/hakimel/reveal.js/blob/master/css/theme/source). It will be automatically compiled by Grunt from Sass to CSS (see the [Gruntfile](https://github.com/hakimel/reveal.js/blob/master/Gruntfile.js)) when you run `npm run build -- css-themes`. 8 | 9 | Each theme file does four things in the following order: 10 | 11 | 1. **Include [/css/theme/template/mixins.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/mixins.scss)** 12 | Shared utility functions. 13 | 14 | 2. **Include [/css/theme/template/settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss)** 15 | Declares a set of custom variables that the template file (step 4) expects. Can be overridden in step 3. 16 | 17 | 3. **Override** 18 | This is where you override the default theme. Either by specifying variables (see [settings.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/settings.scss) for reference) or by adding any selectors and styles you please. 19 | 20 | 4. **Include [/css/theme/template/theme.scss](https://github.com/hakimel/reveal.js/blob/master/css/theme/template/theme.scss)** 21 | The template theme file which will generate final CSS output based on the currently defined variables. 22 | -------------------------------------------------------------------------------- /docs/css/theme/images/nt_logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/css/theme/images/nt_logo_small.png -------------------------------------------------------------------------------- /docs/css/theme/images/spring_io_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/css/theme/images/spring_io_logo.png -------------------------------------------------------------------------------- /docs/css/theme/source/beige.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Beige theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | 15 | // Include theme-specific fonts 16 | @import url(../../lib/font/league-gothic/league-gothic.css); 17 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 18 | 19 | 20 | // Override theme settings (see ../template/settings.scss) 21 | $mainColor: #333; 22 | $headingColor: #333; 23 | $headingTextShadow: none; 24 | $backgroundColor: #f7f3de; 25 | $linkColor: #8b743d; 26 | $linkColorHover: lighten( $linkColor, 20% ); 27 | $selectionBackgroundColor: rgba(79, 64, 28, 0.99); 28 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 29 | 30 | // Background generator 31 | @mixin bodyBackground() { 32 | @include radial-gradient( rgba(247,242,211,1), rgba(255,255,255,1) ); 33 | } 34 | 35 | 36 | 37 | // Theme template ------------------------------ 38 | @import "../template/theme"; 39 | // --------------------------------------------- -------------------------------------------------------------------------------- /docs/css/theme/source/black.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Black theme for reveal.js. This is the opposite of the 'white' theme. 3 | * 4 | * By Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/source-sans-pro/source-sans-pro.css); 16 | 17 | 18 | // Override theme settings (see ../template/settings.scss) 19 | $backgroundColor: #191919; 20 | 21 | $mainColor: #fff; 22 | $headingColor: #fff; 23 | 24 | $mainFontSize: 42px; 25 | $mainFont: 'Source Sans Pro', Helvetica, sans-serif; 26 | $headingFont: 'Source Sans Pro', Helvetica, sans-serif; 27 | $headingTextShadow: none; 28 | $headingLetterSpacing: normal; 29 | $headingTextTransform: uppercase; 30 | $headingFontWeight: 600; 31 | $linkColor: #42affa; 32 | $linkColorHover: lighten( $linkColor, 15% ); 33 | $selectionBackgroundColor: lighten( $linkColor, 25% ); 34 | 35 | $heading1Size: 2.5em; 36 | $heading2Size: 1.6em; 37 | $heading3Size: 1.3em; 38 | $heading4Size: 1.0em; 39 | 40 | section.has-light-background { 41 | &, h1, h2, h3, h4, h5, h6 { 42 | color: #222; 43 | } 44 | } 45 | 46 | 47 | // Theme template ------------------------------ 48 | @import "../template/theme"; 49 | // --------------------------------------------- -------------------------------------------------------------------------------- /docs/css/theme/source/blood.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Blood theme for reveal.js 3 | * Author: Walther http://github.com/Walther 4 | * 5 | * Designed to be used with highlight.js theme 6 | * "monokai_sublime.css" available from 7 | * https://github.com/isagalaev/highlight.js/ 8 | * 9 | * For other themes, change $codeBackground accordingly. 10 | * 11 | */ 12 | 13 | // Default mixins and settings ----------------- 14 | @import "../template/mixins"; 15 | @import "../template/settings"; 16 | // --------------------------------------------- 17 | 18 | // Include theme-specific fonts 19 | 20 | @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,700,300italic,700italic); 21 | 22 | // Colors used in the theme 23 | $blood: #a23; 24 | $coal: #222; 25 | $codeBackground: #23241f; 26 | 27 | $backgroundColor: $coal; 28 | 29 | // Main text 30 | $mainFont: Ubuntu, 'sans-serif'; 31 | $mainColor: #eee; 32 | 33 | // Headings 34 | $headingFont: Ubuntu, 'sans-serif'; 35 | $headingTextShadow: 2px 2px 2px $coal; 36 | 37 | // h1 shadow, borrowed humbly from 38 | // (c) Default theme by Hakim El Hattab 39 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 40 | 41 | // Links 42 | $linkColor: $blood; 43 | $linkColorHover: lighten( $linkColor, 20% ); 44 | 45 | // Text selection 46 | $selectionBackgroundColor: $blood; 47 | $selectionColor: #fff; 48 | 49 | 50 | // Theme template ------------------------------ 51 | @import "../template/theme"; 52 | // --------------------------------------------- 53 | 54 | // some overrides after theme template import 55 | 56 | .reveal p { 57 | font-weight: 300; 58 | text-shadow: 1px 1px $coal; 59 | } 60 | 61 | .reveal h1, 62 | .reveal h2, 63 | .reveal h3, 64 | .reveal h4, 65 | .reveal h5, 66 | .reveal h6 { 67 | font-weight: 700; 68 | } 69 | 70 | .reveal p code { 71 | background-color: $codeBackground; 72 | display: inline-block; 73 | border-radius: 7px; 74 | } 75 | 76 | .reveal small code { 77 | vertical-align: baseline; 78 | } -------------------------------------------------------------------------------- /docs/css/theme/source/league.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * League theme for reveal.js. 3 | * 4 | * This was the default theme pre-3.0.0. 5 | * 6 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 7 | */ 8 | 9 | 10 | // Default mixins and settings ----------------- 11 | @import "../template/mixins"; 12 | @import "../template/settings"; 13 | // --------------------------------------------- 14 | 15 | 16 | 17 | // Include theme-specific fonts 18 | @import url(../../lib/font/league-gothic/league-gothic.css); 19 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 20 | 21 | // Override theme settings (see ../template/settings.scss) 22 | $headingTextShadow: 0px 0px 6px rgba(0,0,0,0.2); 23 | $heading1TextShadow: 0 1px 0 #ccc, 0 2px 0 #c9c9c9, 0 3px 0 #bbb, 0 4px 0 #b9b9b9, 0 5px 0 #aaa, 0 6px 1px rgba(0,0,0,.1), 0 0 5px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,.2), 0 5px 10px rgba(0,0,0,.25), 0 20px 20px rgba(0,0,0,.15); 24 | 25 | // Background generator 26 | @mixin bodyBackground() { 27 | @include radial-gradient( rgba(28,30,32,1), rgba(85,90,95,1) ); 28 | } 29 | 30 | 31 | 32 | // Theme template ------------------------------ 33 | @import "../template/theme"; 34 | // --------------------------------------------- -------------------------------------------------------------------------------- /docs/css/theme/source/moon.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Dark theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | /** 19 | * Solarized colors by Ethan Schoonover 20 | */ 21 | html * { 22 | color-profile: sRGB; 23 | rendering-intent: auto; 24 | } 25 | 26 | // Solarized colors 27 | $base03: #002b36; 28 | $base02: #073642; 29 | $base01: #586e75; 30 | $base00: #657b83; 31 | $base0: #839496; 32 | $base1: #93a1a1; 33 | $base2: #eee8d5; 34 | $base3: #fdf6e3; 35 | $yellow: #b58900; 36 | $orange: #cb4b16; 37 | $red: #dc322f; 38 | $magenta: #d33682; 39 | $violet: #6c71c4; 40 | $blue: #268bd2; 41 | $cyan: #2aa198; 42 | $green: #859900; 43 | 44 | // Override theme settings (see ../template/settings.scss) 45 | $mainColor: $base1; 46 | $headingColor: $base2; 47 | $headingTextShadow: none; 48 | $backgroundColor: $base03; 49 | $linkColor: $blue; 50 | $linkColorHover: lighten( $linkColor, 20% ); 51 | $selectionBackgroundColor: $magenta; 52 | 53 | 54 | 55 | // Theme template ------------------------------ 56 | @import "../template/theme"; 57 | // --------------------------------------------- 58 | -------------------------------------------------------------------------------- /docs/css/theme/source/night.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Black theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(https://fonts.googleapis.com/css?family=Montserrat:700); 16 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic,700italic); 17 | 18 | 19 | // Override theme settings (see ../template/settings.scss) 20 | $backgroundColor: #111; 21 | 22 | $mainFont: 'Open Sans', sans-serif; 23 | $linkColor: #e7ad52; 24 | $linkColorHover: lighten( $linkColor, 20% ); 25 | $headingFont: 'Montserrat', Impact, sans-serif; 26 | $headingTextShadow: none; 27 | $headingLetterSpacing: -0.03em; 28 | $headingTextTransform: none; 29 | $selectionBackgroundColor: #e7ad52; 30 | 31 | 32 | // Theme template ------------------------------ 33 | @import "../template/theme"; 34 | // --------------------------------------------- -------------------------------------------------------------------------------- /docs/css/theme/source/novatec.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Dark theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | /** 19 | * Solarized colors by Ethan Schoonover 20 | */ 21 | html * { 22 | color-profile: sRGB; 23 | rendering-intent: auto; 24 | } 25 | 26 | // Solarized colors 27 | $base03: #002b36; 28 | $base02: #073642; 29 | $base01: #586e75; 30 | $base00: #657b83; 31 | $base0: #839496; 32 | $base1: #93a1a1; 33 | $base2: #eee8d5; 34 | $base3: #fdf6e3; 35 | $yellow: #b58900; 36 | $orange: #cb4b16; 37 | $red: #dc322f; 38 | $magenta: #d33682; 39 | $violet: #6c71c4; 40 | $blue: #268bd2; 41 | $cyan: #2aa198; 42 | $green: #859900; 43 | 44 | // Override theme settings (see ../template/settings.scss) 45 | $mainColor: $base1; 46 | $headingColor: $base2; 47 | $headingTextShadow: none; 48 | $backgroundColor: $base03; 49 | $linkColor: $blue; 50 | $linkColorHover: lighten( $linkColor, 20% ); 51 | $selectionBackgroundColor: $magenta; 52 | 53 | 54 | 55 | // Theme template ------------------------------ 56 | @import "../template/theme"; 57 | // --------------------------------------------- 58 | -------------------------------------------------------------------------------- /docs/css/theme/source/serif.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple theme for reveal.js presentations, similar 3 | * to the default theme. The accent color is brown. 4 | * 5 | * This theme is Copyright (C) 2012-2013 Owen Versteeg, http://owenversteeg.com - it is MIT licensed. 6 | */ 7 | 8 | 9 | // Default mixins and settings ----------------- 10 | @import "../template/mixins"; 11 | @import "../template/settings"; 12 | // --------------------------------------------- 13 | 14 | 15 | 16 | // Override theme settings (see ../template/settings.scss) 17 | $mainFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 18 | $mainColor: #000; 19 | $headingFont: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 20 | $headingColor: #383D3D; 21 | $headingTextShadow: none; 22 | $headingTextTransform: none; 23 | $backgroundColor: #F0F1EB; 24 | $linkColor: #51483D; 25 | $linkColorHover: lighten( $linkColor, 20% ); 26 | $selectionBackgroundColor: #26351C; 27 | 28 | .reveal a { 29 | line-height: 1.3em; 30 | } 31 | 32 | 33 | // Theme template ------------------------------ 34 | @import "../template/theme"; 35 | // --------------------------------------------- 36 | -------------------------------------------------------------------------------- /docs/css/theme/source/simple.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple theme for reveal.js presentations, similar 3 | * to the default theme. The accent color is darkblue. 4 | * 5 | * This theme is Copyright (C) 2012 Owen Versteeg, https://github.com/StereotypicalApps. It is MIT licensed. 6 | * reveal.js is Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 7 | */ 8 | 9 | 10 | // Default mixins and settings ----------------- 11 | @import "../template/mixins"; 12 | @import "../template/settings"; 13 | // --------------------------------------------- 14 | 15 | 16 | 17 | // Include theme-specific fonts 18 | @import url(https://fonts.googleapis.com/css?family=News+Cycle:400,700); 19 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 20 | 21 | 22 | // Override theme settings (see ../template/settings.scss) 23 | $mainFont: 'Lato', sans-serif; 24 | $mainColor: #000; 25 | $headingFont: 'News Cycle', Impact, sans-serif; 26 | $headingColor: #000; 27 | $headingTextShadow: none; 28 | $headingTextTransform: none; 29 | $backgroundColor: #fff; 30 | $linkColor: #00008B; 31 | $linkColorHover: lighten( $linkColor, 20% ); 32 | $selectionBackgroundColor: rgba(0, 0, 0, 0.99); 33 | 34 | section.has-dark-background { 35 | &, h1, h2, h3, h4, h5, h6 { 36 | color: #fff; 37 | } 38 | } 39 | 40 | 41 | // Theme template ------------------------------ 42 | @import "../template/theme"; 43 | // --------------------------------------------- -------------------------------------------------------------------------------- /docs/css/theme/source/sky.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Sky theme for reveal.js. 3 | * 4 | * Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | 15 | // Include theme-specific fonts 16 | @import url(https://fonts.googleapis.com/css?family=Quicksand:400,700,400italic,700italic); 17 | @import url(https://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700); 18 | 19 | 20 | // Override theme settings (see ../template/settings.scss) 21 | $mainFont: 'Open Sans', sans-serif; 22 | $mainColor: #333; 23 | $headingFont: 'Quicksand', sans-serif; 24 | $headingColor: #333; 25 | $headingLetterSpacing: -0.08em; 26 | $headingTextShadow: none; 27 | $backgroundColor: #f7fbfc; 28 | $linkColor: #3b759e; 29 | $linkColorHover: lighten( $linkColor, 20% ); 30 | $selectionBackgroundColor: #134674; 31 | 32 | // Fix links so they are not cut off 33 | .reveal a { 34 | line-height: 1.3em; 35 | } 36 | 37 | // Background generator 38 | @mixin bodyBackground() { 39 | @include radial-gradient( #add9e4, #f7fbfc ); 40 | } 41 | 42 | 43 | 44 | // Theme template ------------------------------ 45 | @import "../template/theme"; 46 | // --------------------------------------------- 47 | -------------------------------------------------------------------------------- /docs/css/theme/source/solarized.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Light theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | 19 | /** 20 | * Solarized colors by Ethan Schoonover 21 | */ 22 | html * { 23 | color-profile: sRGB; 24 | rendering-intent: auto; 25 | } 26 | 27 | // Solarized colors 28 | $base03: #002b36; 29 | $base02: #073642; 30 | $base01: #586e75; 31 | $base00: #657b83; 32 | $base0: #839496; 33 | $base1: #93a1a1; 34 | $base2: #eee8d5; 35 | $base3: #fdf6e3; 36 | $yellow: #b58900; 37 | $orange: #cb4b16; 38 | $red: #dc322f; 39 | $magenta: #d33682; 40 | $violet: #6c71c4; 41 | $blue: #268bd2; 42 | $cyan: #2aa198; 43 | $green: #859900; 44 | 45 | // Override theme settings (see ../template/settings.scss) 46 | $mainColor: $base00; 47 | $headingColor: $base01; 48 | $headingTextShadow: none; 49 | $backgroundColor: $base3; 50 | $linkColor: $blue; 51 | $linkColorHover: lighten( $linkColor, 20% ); 52 | $selectionBackgroundColor: $magenta; 53 | 54 | // Background generator 55 | // @mixin bodyBackground() { 56 | // @include radial-gradient( rgba($base3,1), rgba(lighten($base3, 20%),1) ); 57 | // } 58 | 59 | 60 | 61 | // Theme template ------------------------------ 62 | @import "../template/theme"; 63 | // --------------------------------------------- 64 | -------------------------------------------------------------------------------- /docs/css/theme/source/spring_io.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Solarized Dark theme for reveal.js. 3 | * Author: Achim Staebler 4 | */ 5 | 6 | 7 | // Default mixins and settings ----------------- 8 | @import "../template/mixins"; 9 | @import "../template/settings"; 10 | // --------------------------------------------- 11 | 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/league-gothic/league-gothic.css); 16 | @import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); 17 | 18 | /** 19 | * Solarized colors by Ethan Schoonover 20 | */ 21 | html * { 22 | color-profile: sRGB; 23 | rendering-intent: auto; 24 | } 25 | 26 | // Solarized colors 27 | $base03: #002b36; 28 | $base02: #073642; 29 | $base01: #586e75; 30 | $base00: #657b83; 31 | $base0: #839496; 32 | $base1: #93a1a1; 33 | $base2: #eee8d5; 34 | $base3: #fdf6e3; 35 | $yellow: #b58900; 36 | $orange: #cb4b16; 37 | $red: #dc322f; 38 | $magenta: #d33682; 39 | $violet: #6c71c4; 40 | $blue: #268bd2; 41 | $cyan: #2aa198; 42 | $green: #859900; 43 | 44 | // Override theme settings (see ../template/settings.scss) 45 | $mainColor: $base1; 46 | $headingColor: $base2; 47 | $headingTextShadow: none; 48 | $backgroundColor: $base03; 49 | $linkColor: $blue; 50 | $linkColorHover: lighten( $linkColor, 20% ); 51 | $selectionBackgroundColor: $magenta; 52 | 53 | 54 | 55 | // Theme template ------------------------------ 56 | @import "../template/theme"; 57 | // --------------------------------------------- 58 | -------------------------------------------------------------------------------- /docs/css/theme/source/white.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * White theme for reveal.js. This is the opposite of the 'black' theme. 3 | * 4 | * By Hakim El Hattab, http://hakim.se 5 | */ 6 | 7 | 8 | // Default mixins and settings ----------------- 9 | @import "../template/mixins"; 10 | @import "../template/settings"; 11 | // --------------------------------------------- 12 | 13 | 14 | // Include theme-specific fonts 15 | @import url(../../lib/font/source-sans-pro/source-sans-pro.css); 16 | 17 | 18 | // Override theme settings (see ../template/settings.scss) 19 | $backgroundColor: #fff; 20 | 21 | $mainColor: #222; 22 | $headingColor: #222; 23 | 24 | $mainFontSize: 42px; 25 | $mainFont: 'Source Sans Pro', Helvetica, sans-serif; 26 | $headingFont: 'Source Sans Pro', Helvetica, sans-serif; 27 | $headingTextShadow: none; 28 | $headingLetterSpacing: normal; 29 | $headingTextTransform: uppercase; 30 | $headingFontWeight: 600; 31 | $linkColor: #2a76dd; 32 | $linkColorHover: lighten( $linkColor, 15% ); 33 | $selectionBackgroundColor: lighten( $linkColor, 25% ); 34 | 35 | $heading1Size: 2.5em; 36 | $heading2Size: 1.6em; 37 | $heading3Size: 1.3em; 38 | $heading4Size: 1.0em; 39 | 40 | section.has-dark-background { 41 | &, h1, h2, h3, h4, h5, h6 { 42 | color: #fff; 43 | } 44 | } 45 | 46 | 47 | // Theme template ------------------------------ 48 | @import "../template/theme"; 49 | // --------------------------------------------- -------------------------------------------------------------------------------- /docs/css/theme/template/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin vertical-gradient( $top, $bottom ) { 2 | background: $top; 3 | background: -moz-linear-gradient( top, $top 0%, $bottom 100% ); 4 | background: -webkit-gradient( linear, left top, left bottom, color-stop(0%,$top), color-stop(100%,$bottom) ); 5 | background: -webkit-linear-gradient( top, $top 0%, $bottom 100% ); 6 | background: -o-linear-gradient( top, $top 0%, $bottom 100% ); 7 | background: -ms-linear-gradient( top, $top 0%, $bottom 100% ); 8 | background: linear-gradient( top, $top 0%, $bottom 100% ); 9 | } 10 | 11 | @mixin horizontal-gradient( $top, $bottom ) { 12 | background: $top; 13 | background: -moz-linear-gradient( left, $top 0%, $bottom 100% ); 14 | background: -webkit-gradient( linear, left top, right top, color-stop(0%,$top), color-stop(100%,$bottom) ); 15 | background: -webkit-linear-gradient( left, $top 0%, $bottom 100% ); 16 | background: -o-linear-gradient( left, $top 0%, $bottom 100% ); 17 | background: -ms-linear-gradient( left, $top 0%, $bottom 100% ); 18 | background: linear-gradient( left, $top 0%, $bottom 100% ); 19 | } 20 | 21 | @mixin radial-gradient( $outer, $inner, $type: circle ) { 22 | background: $outer; 23 | background: -moz-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 24 | background: -webkit-gradient( radial, center center, 0px, center center, 100%, color-stop(0%,$inner), color-stop(100%,$outer) ); 25 | background: -webkit-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 26 | background: -o-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 27 | background: -ms-radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 28 | background: radial-gradient( center, $type cover, $inner 0%, $outer 100% ); 29 | } -------------------------------------------------------------------------------- /docs/css/theme/template/settings.scss: -------------------------------------------------------------------------------- 1 | // Base settings for all themes that can optionally be 2 | // overridden by the super-theme 3 | 4 | // Background of the presentation 5 | $backgroundColor: #2b2b2b; 6 | 7 | // Primary/body text 8 | $mainFont: 'Lato', sans-serif; 9 | $mainFontSize: 40px; 10 | $mainColor: #eee; 11 | 12 | // Vertical spacing between blocks of text 13 | $blockMargin: 20px; 14 | 15 | // Headings 16 | $headingMargin: 0 0 $blockMargin 0; 17 | $headingFont: 'League Gothic', Impact, sans-serif; 18 | $headingColor: #eee; 19 | $headingLineHeight: 1.2; 20 | $headingLetterSpacing: normal; 21 | $headingTextTransform: uppercase; 22 | $headingTextShadow: none; 23 | $headingFontWeight: normal; 24 | $heading1TextShadow: $headingTextShadow; 25 | 26 | $heading1Size: 3.77em; 27 | $heading2Size: 2.11em; 28 | $heading3Size: 1.55em; 29 | $heading4Size: 1.00em; 30 | 31 | $codeFont: monospace; 32 | 33 | // Links and actions 34 | $linkColor: #13DAEC; 35 | $linkColorHover: lighten( $linkColor, 20% ); 36 | 37 | // Text selection 38 | $selectionBackgroundColor: #FF5E99; 39 | $selectionColor: #fff; 40 | 41 | // Generates the presentation background, can be overridden 42 | // to return a background image or gradient 43 | @mixin bodyBackground() { 44 | background: $backgroundColor; 45 | } 46 | -------------------------------------------------------------------------------- /docs/gruntfile.js: -------------------------------------------------------------------------------- 1 | const sass = require('node-sass'); 2 | 3 | module.exports = grunt => { 4 | 5 | require('load-grunt-tasks')(grunt); 6 | 7 | let port = grunt.option('port') || 8000; 8 | let root = grunt.option('root') || '.'; 9 | 10 | if (!Array.isArray(root)) root = [root]; 11 | 12 | // Project configuration 13 | grunt.initConfig({ 14 | pkg: grunt.file.readJSON('package.json'), 15 | meta: { 16 | banner: 17 | '/*!\n' + 18 | ' * reveal.js <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd, HH:MM") %>)\n' + 19 | ' * http://revealjs.com\n' + 20 | ' * MIT licensed\n' + 21 | ' *\n' + 22 | ' * Copyright (C) 2019 Hakim El Hattab, http://hakim.se\n' + 23 | ' */' 24 | }, 25 | 26 | qunit: { 27 | files: [ 'test/*.html' ] 28 | }, 29 | 30 | uglify: { 31 | options: { 32 | banner: '<%= meta.banner %>\n', 33 | ie8: true 34 | }, 35 | build: { 36 | src: 'js/reveal.js', 37 | dest: 'js/reveal.min.js' 38 | } 39 | }, 40 | 41 | sass: { 42 | options: { 43 | implementation: sass, 44 | sourceMap: false 45 | }, 46 | core: { 47 | src: 'css/reveal.scss', 48 | dest: 'css/reveal.css' 49 | }, 50 | themes: { 51 | expand: true, 52 | cwd: 'css/theme/source', 53 | src: ['*.sass', '*.scss'], 54 | dest: 'css/theme', 55 | ext: '.css' 56 | } 57 | }, 58 | 59 | autoprefixer: { 60 | core: { 61 | src: 'css/reveal.css' 62 | } 63 | }, 64 | 65 | cssmin: { 66 | options: { 67 | compatibility: 'ie9' 68 | }, 69 | compress: { 70 | src: 'css/reveal.css', 71 | dest: 'css/reveal.min.css' 72 | } 73 | }, 74 | 75 | jshint: { 76 | options: { 77 | curly: false, 78 | eqeqeq: true, 79 | immed: true, 80 | esnext: true, 81 | latedef: 'nofunc', 82 | newcap: true, 83 | noarg: true, 84 | sub: true, 85 | undef: true, 86 | eqnull: true, 87 | browser: true, 88 | expr: true, 89 | loopfunc: true, 90 | globals: { 91 | head: false, 92 | module: false, 93 | console: false, 94 | unescape: false, 95 | define: false, 96 | exports: false, 97 | require: false 98 | } 99 | }, 100 | files: [ 'gruntfile.js', 'js/reveal.js' ] 101 | }, 102 | 103 | connect: { 104 | server: { 105 | options: { 106 | port: port, 107 | base: root, 108 | livereload: true, 109 | open: true, 110 | useAvailablePort: true 111 | } 112 | } 113 | }, 114 | 115 | zip: { 116 | bundle: { 117 | src: [ 118 | 'index.html', 119 | 'css/**', 120 | 'js/**', 121 | 'lib/**', 122 | 'images/**', 123 | 'plugin/**', 124 | '**.md' 125 | ], 126 | dest: 'reveal-js-presentation.zip' 127 | } 128 | }, 129 | 130 | watch: { 131 | js: { 132 | files: [ 'gruntfile.js', 'js/reveal.js' ], 133 | tasks: 'js' 134 | }, 135 | theme: { 136 | files: [ 137 | 'css/theme/source/*.sass', 138 | 'css/theme/source/*.scss', 139 | 'css/theme/template/*.sass', 140 | 'css/theme/template/*.scss' 141 | ], 142 | tasks: 'css-themes' 143 | }, 144 | css: { 145 | files: [ 'css/reveal.scss' ], 146 | tasks: 'css-core' 147 | }, 148 | test: { 149 | files: [ 'test/*.html' ], 150 | tasks: 'test' 151 | }, 152 | html: { 153 | files: root.map(path => path + '/*.html') 154 | }, 155 | markdown: { 156 | files: root.map(path => path + '/*.md') 157 | }, 158 | options: { 159 | livereload: true 160 | } 161 | } 162 | 163 | }); 164 | 165 | grunt.loadNpmTasks('grunt-contrib-clean'); 166 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 167 | 168 | // Default task 169 | grunt.registerTask( 'default', [ 'css', 'js' ] ); 170 | 171 | // JS task 172 | grunt.registerTask( 'js', [ 'jshint', 'uglify', 'qunit' ] ); 173 | 174 | // Theme CSS 175 | grunt.registerTask( 'css-themes', [ 'sass:themes' ] ); 176 | 177 | // Core framework CSS 178 | grunt.registerTask( 'css-core', [ 'sass:core', 'autoprefixer', 'cssmin' ] ); 179 | 180 | // All CSS 181 | grunt.registerTask( 'css', [ 'sass', 'autoprefixer', 'cssmin' ] ); 182 | 183 | // Package presentation to archive 184 | grunt.registerTask( 'package', [ 'default', 'zip' ] ); 185 | 186 | // Serve presentation locally 187 | grunt.registerTask( 'serve', [ 'connect', 'watch' ] ); 188 | 189 | // Run tests 190 | grunt.registerTask( 'test', [ 'jshint', 'qunit' ] ); 191 | 192 | }; 193 | -------------------------------------------------------------------------------- /docs/images/agile-security-book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/agile-security-book.jpg -------------------------------------------------------------------------------- /docs/images/alpine_docker_cve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/alpine_docker_cve.png -------------------------------------------------------------------------------- /docs/images/andreas_falk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/andreas_falk.jpg -------------------------------------------------------------------------------- /docs/images/cropped-novatec-favicon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/cropped-novatec-favicon-192x192.png -------------------------------------------------------------------------------- /docs/images/cropped-novatec-favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/cropped-novatec-favicon-32x32.png -------------------------------------------------------------------------------- /docs/images/devops-handbook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/devops-handbook.jpg -------------------------------------------------------------------------------- /docs/images/docker_images_vulnerabilities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/docker_images_vulnerabilities.png -------------------------------------------------------------------------------- /docs/images/encryption_layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/encryption_layers.png -------------------------------------------------------------------------------- /docs/images/encryption_layers.xml: -------------------------------------------------------------------------------- 1 | xVZNk5NAEP01Oa5F+BKPMZvVw26pG6vU42SmA+iExmFIwF9vA02AJLuuVbsKB6Zfd89Hv9dTzLzlrnpnRJ7coQI9cx1VzbzrmevOvTc+fRqk7pBozkBsUsVBA7BOfwGDDqNlqqCYBFpEbdN8CkrMMpB2gglj8DAN26KerpqLGM6AtRT6HP2SKpswOnecwfEe0jjhpaOAHTvRBzNQJELhYQR5q5m3NIi2G+2qJeimeH1durybB7zHjRnI7FMSbsAL9vd3LizQW39UH6rq0+Eq4L3Zuj8wKDo/m2hsgjFmQq8G9K3BMlPQzOqQNcTcIuYEzgn8DtbWTKYoLRKU2J1mL23Y1F+b/FdBb34b+64rnryzarYKK4xdNKQSILUoilT28E2q++kLa/DHkS2fkC05l6jRtEf0tpEEKY+RI4/TPuTpytLU4sFqM1RgaSQ8UuJetcLEYB+J84+aoGYC3AEdnfIMaGHT/XQfglUdH+MG4mnA3P+FDnjevdAlrzRzQ22b2iEdeKyQ8GeJveOqaDleUIDr59XgpFHM33aWTQ+sQRqwBSWsMmnq3KaYkbFo1riHwvYJdIrN6SSEdbvp4RPpToV5SFIL61y05BzodpqK8FQTQfNe0kTYPnzgEd49x33swVioHlfLObucEPEdwZek77N9GK6c1/09koxum3kf+OyC8F5IEKvPy+un8kk3Zt4MFcpy11byT6RuOgXcbh5iebt1L3e+CjdhED4Pm6F3Qmd4Tuc8uESn81J0+v+qvxd5rlMpuK1vRQ2Gvpe7/v90ugogUv4lDUTuxgufSQMnHR29YEOTOfxFtL7Rv5i3+g0= -------------------------------------------------------------------------------- /docs/images/envelope_encryption.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/envelope_encryption.png -------------------------------------------------------------------------------- /docs/images/envelope_encryption.xml: -------------------------------------------------------------------------------- 1 | 7Vldj5s4FP01kXYfGmEbGPLYfHRXmqlUNQ/d7UvlgAPuGEwdp0n663vBBuJAOq3mI6ttMhNin3vt4HvOdS4wIrN8/5eiZfZWJkyMsJfsR2Q+whiRiQ8fFXIwSIQskCqeWKcOWPJvzIKeRbc8YRvHUUspNC9dMJZFwWLtYFQpuXPd1lK431rSlPWAZUxFH/3AE51ZFHleZ/ib8TSzXx0F1pDTxtkCm4wmcncEkcWIzJSU2rTy/YyJKnhNXMy4N2es7YkpVuifGbDCyzj69jGcoo+48N9/XrDy7hWy03ylYmtXbM9WH5oQKLktElbN4o3IdJdxzZYljSvrDkgHLNO5gB6C5poLMZNCqnosSQIWJT7gG63kPTuyRHhFwhAs9gSY0mx/dmmoDRgojcmcaXUAFzsgatiwIsO+7e86ylpFZUds4dCC1MokbefuIgkNG8xfCCweiGsodBUhCYs6DnD4ZSsbw6tNnQGvwQFF5b4zQiutPj0PeZX64N+rVmTmXKnWjowVGTdoDvgYp+pY+VaeZyaq/4xT4wHRMCswbv2BPf0Y6pvswa5eICvKyi/fp9UGMl4LuYszqvQ4kfE2BwHgM6IzkZp7Yxz0dbder3EcD+kuCVdh8ES6w/6DskOTF5UdeTideV5vecckCLpiYkrj+7TOdTeO8AIXO2rO8xROTPAVHGPBy09AFTRTVjBF4fTe3Mn4/hPC0R7e47JI+9zM6tfTMHBCAOoTEA3EP3qu8PvXrP//Z71PHv61eeG0D363tEcnFFw68cOLE3DLDpsXJCAKHAJavR/nABlgAOHnogCRXshZArW87UqlM5nKgopFh07d2rbzuZOytCR9Zlof7IUJ3WrpUgjxUod/qs7Y84IG+LfeogISNcB8b7/C9A7HvXdMcQgBUxZ0d86hbS6K2fA2t4oCP/Baiqv1/5hgCJfcqpj9IK43xk9TlTL90G9fXzCKCar5V/c8npz8m98s/yLf3QAJvnj+RY+ufbA/VPu0tUYDLIpYHUpIWuzNqaZHJcrqdEyvbDnRBBCgTyoTJ6UKWbATIi1EBU8L6MZAWJW504pOHlPx2hpyniT1FjNUxbjbzhPoAbmFcEAGLoDxUEnyXGqYXEwNnkE0lwV0IC3h+Md8cfvnVSh15RT8x5TSlG7PLxWjhSF53F7lYaw3E1ce/tCdtJeVx+PvpJHw5+TxdnnVQK2Bk4ur8OIa8K+l/S+X9o8oxe3Qd5LXCXbmvge5mYwDdxJzMWHHdaT3psLNc4fzU5nrjd5UtX7aNQ1JCrrdQxXj3j2aIovv -------------------------------------------------------------------------------- /docs/images/helm_3_tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/helm_3_tweet.png -------------------------------------------------------------------------------- /docs/images/iron-glad-java.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/iron-glad-java.jpg -------------------------------------------------------------------------------- /docs/images/kubernetes_basics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/kubernetes_basics.png -------------------------------------------------------------------------------- /docs/images/novatec-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/novatec-logo.png -------------------------------------------------------------------------------- /docs/images/novatec_logo_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/novatec_logo_big.png -------------------------------------------------------------------------------- /docs/images/novatec_offices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/novatec_offices.png -------------------------------------------------------------------------------- /docs/images/oauth2_in_action.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/oauth2_in_action.jpg -------------------------------------------------------------------------------- /docs/images/owasp-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/owasp-logo.png -------------------------------------------------------------------------------- /docs/images/secrets_env_vars_tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/secrets_env_vars_tweet.png -------------------------------------------------------------------------------- /docs/images/spring-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/spring-logo.png -------------------------------------------------------------------------------- /docs/images/spring_io_2019_workshop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/spring_io_2019_workshop.jpg -------------------------------------------------------------------------------- /docs/images/spring_io_kubernetes_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/spring_io_kubernetes_cloud.png -------------------------------------------------------------------------------- /docs/images/threat-modeling-book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/threat-modeling-book.jpg -------------------------------------------------------------------------------- /docs/images/title_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/title_image.png -------------------------------------------------------------------------------- /docs/images/tweet_kubernetes_all_root.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/images/tweet_kubernetes_all_root.png -------------------------------------------------------------------------------- /docs/lib/css/monokai.css: -------------------------------------------------------------------------------- 1 | /* 2 | Monokai style - ported by Luigi Maselli - http://grigio.org 3 | */ 4 | 5 | .hljs { 6 | display: block; 7 | overflow-x: auto; 8 | padding: 0.5em; 9 | background: #272822; 10 | color: #ddd; 11 | } 12 | 13 | .hljs-tag, 14 | .hljs-keyword, 15 | .hljs-selector-tag, 16 | .hljs-literal, 17 | .hljs-strong, 18 | .hljs-name { 19 | color: #f92672; 20 | } 21 | 22 | .hljs-code { 23 | color: #66d9ef; 24 | } 25 | 26 | .hljs-class .hljs-title { 27 | color: white; 28 | } 29 | 30 | .hljs-attribute, 31 | .hljs-symbol, 32 | .hljs-regexp, 33 | .hljs-link { 34 | color: #bf79db; 35 | } 36 | 37 | .hljs-string, 38 | .hljs-bullet, 39 | .hljs-subst, 40 | .hljs-title, 41 | .hljs-section, 42 | .hljs-emphasis, 43 | .hljs-type, 44 | .hljs-built_in, 45 | .hljs-builtin-name, 46 | .hljs-selector-attr, 47 | .hljs-selector-pseudo, 48 | .hljs-addition, 49 | .hljs-variable, 50 | .hljs-template-tag, 51 | .hljs-template-variable { 52 | color: #a6e22e; 53 | } 54 | 55 | .hljs-comment, 56 | .hljs-quote, 57 | .hljs-deletion, 58 | .hljs-meta { 59 | color: #75715e; 60 | } 61 | 62 | .hljs-keyword, 63 | .hljs-selector-tag, 64 | .hljs-literal, 65 | .hljs-doctag, 66 | .hljs-title, 67 | .hljs-section, 68 | .hljs-type, 69 | .hljs-selector-id { 70 | font-weight: bold; 71 | } 72 | -------------------------------------------------------------------------------- /docs/lib/css/zenburn.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Zenburn style from voldmar.ru (c) Vladimir Epifanov 4 | based on dark.css by Ivan Sagalaev 5 | 6 | */ 7 | 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | background: #3f3f3f; 13 | color: #dcdcdc; 14 | } 15 | 16 | .hljs-keyword, 17 | .hljs-selector-tag, 18 | .hljs-tag { 19 | color: #e3ceab; 20 | } 21 | 22 | .hljs-template-tag { 23 | color: #dcdcdc; 24 | } 25 | 26 | .hljs-number { 27 | color: #8cd0d3; 28 | } 29 | 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-attribute { 33 | color: #efdcbc; 34 | } 35 | 36 | .hljs-literal { 37 | color: #efefaf; 38 | } 39 | 40 | .hljs-subst { 41 | color: #8f8f8f; 42 | } 43 | 44 | .hljs-title, 45 | .hljs-name, 46 | .hljs-selector-id, 47 | .hljs-selector-class, 48 | .hljs-section, 49 | .hljs-type { 50 | color: #efef8f; 51 | } 52 | 53 | .hljs-symbol, 54 | .hljs-bullet, 55 | .hljs-link { 56 | color: #dca3a3; 57 | } 58 | 59 | .hljs-deletion, 60 | .hljs-string, 61 | .hljs-built_in, 62 | .hljs-builtin-name { 63 | color: #cc9393; 64 | } 65 | 66 | .hljs-addition, 67 | .hljs-comment, 68 | .hljs-quote, 69 | .hljs-meta { 70 | color: #7f9f7f; 71 | } 72 | 73 | 74 | .hljs-emphasis { 75 | font-style: italic; 76 | } 77 | 78 | .hljs-strong { 79 | font-weight: bold; 80 | } 81 | -------------------------------------------------------------------------------- /docs/lib/font/league-gothic/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License (OFL) 2 | http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL 3 | -------------------------------------------------------------------------------- /docs/lib/font/league-gothic/league-gothic.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'League Gothic'; 3 | src: url('league-gothic.eot'); 4 | src: url('league-gothic.eot?#iefix') format('embedded-opentype'), 5 | url('league-gothic.woff') format('woff'), 6 | url('league-gothic.ttf') format('truetype'); 7 | 8 | font-weight: normal; 9 | font-style: normal; 10 | } -------------------------------------------------------------------------------- /docs/lib/font/league-gothic/league-gothic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/league-gothic/league-gothic.eot -------------------------------------------------------------------------------- /docs/lib/font/league-gothic/league-gothic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/league-gothic/league-gothic.ttf -------------------------------------------------------------------------------- /docs/lib/font/league-gothic/league-gothic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/league-gothic/league-gothic.woff -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/LICENSE: -------------------------------------------------------------------------------- 1 | SIL Open Font License 2 | 3 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 4 | 5 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 6 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 7 | 8 | —————————————————————————————- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | —————————————————————————————- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. 14 | 15 | The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. 16 | 17 | DEFINITIONS 18 | “Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. 19 | 20 | “Reserved Font Name” refers to any names specified as such after the copyright statement(s). 21 | 22 | “Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s). 23 | 24 | “Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. 25 | 26 | “Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. 27 | 28 | PERMISSION & CONDITIONS 29 | Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 30 | 31 | 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 32 | 33 | 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 34 | 35 | 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 36 | 37 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 38 | 39 | 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. 40 | 41 | TERMINATION 42 | This license becomes null and void if any of the above conditions are not met. 43 | 44 | DISCLAIMER 45 | THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-italic.eot -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-italic.ttf -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-italic.woff -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-regular.eot -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-regular.ttf -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-regular.woff -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-semibold.eot -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-semibold.ttf -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-semibold.woff -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-semibolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-semibolditalic.eot -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-semibolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-semibolditalic.ttf -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro-semibolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/docs/lib/font/source-sans-pro/source-sans-pro-semibolditalic.woff -------------------------------------------------------------------------------- /docs/lib/font/source-sans-pro/source-sans-pro.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Source Sans Pro'; 3 | src: url('source-sans-pro-regular.eot'); 4 | src: url('source-sans-pro-regular.eot?#iefix') format('embedded-opentype'), 5 | url('source-sans-pro-regular.woff') format('woff'), 6 | url('source-sans-pro-regular.ttf') format('truetype'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'Source Sans Pro'; 13 | src: url('source-sans-pro-italic.eot'); 14 | src: url('source-sans-pro-italic.eot?#iefix') format('embedded-opentype'), 15 | url('source-sans-pro-italic.woff') format('woff'), 16 | url('source-sans-pro-italic.ttf') format('truetype'); 17 | font-weight: normal; 18 | font-style: italic; 19 | } 20 | 21 | @font-face { 22 | font-family: 'Source Sans Pro'; 23 | src: url('source-sans-pro-semibold.eot'); 24 | src: url('source-sans-pro-semibold.eot?#iefix') format('embedded-opentype'), 25 | url('source-sans-pro-semibold.woff') format('woff'), 26 | url('source-sans-pro-semibold.ttf') format('truetype'); 27 | font-weight: 600; 28 | font-style: normal; 29 | } 30 | 31 | @font-face { 32 | font-family: 'Source Sans Pro'; 33 | src: url('source-sans-pro-semibolditalic.eot'); 34 | src: url('source-sans-pro-semibolditalic.eot?#iefix') format('embedded-opentype'), 35 | url('source-sans-pro-semibolditalic.woff') format('woff'), 36 | url('source-sans-pro-semibolditalic.ttf') format('truetype'); 37 | font-weight: 600; 38 | font-style: italic; 39 | } -------------------------------------------------------------------------------- /docs/lib/js/html5shiv.js: -------------------------------------------------------------------------------- 1 | document.createElement('header'); 2 | document.createElement('nav'); 3 | document.createElement('section'); 4 | document.createElement('article'); 5 | document.createElement('aside'); 6 | document.createElement('footer'); 7 | document.createElement('hgroup'); -------------------------------------------------------------------------------- /docs/lib/js/promise.js: -------------------------------------------------------------------------------- 1 | /* MIT | https://github.com/taylorhakes/promise-polyfill */ 2 | !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n():"function"==typeof define&&define.amd?define(n):n()}(0,function(){"use strict";function e(e){var n=this.constructor;return this.then(function(t){return n.resolve(e()).then(function(){return t})},function(t){return n.resolve(e()).then(function(){return n.reject(t)})})}function n(){}function t(e){if(!(this instanceof t))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=undefined,this._deferreds=[],u(e,this)}function o(e,n){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,t._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null!==t){var o;try{o=t(e._value)}catch(f){return void i(n.promise,f)}r(n.promise,o)}else(1===e._state?r:i)(n.promise,e._value)})):e._deferreds.push(n)}function r(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var o=n.then;if(n instanceof t)return e._state=3,e._value=n,void f(e);if("function"==typeof o)return void u(function(e,n){return function(){e.apply(n,arguments)}}(o,n),e)}e._state=1,e._value=n,f(e)}catch(r){i(e,r)}}function i(e,n){e._state=2,e._value=n,f(e)}function f(e){2===e._state&&0===e._deferreds.length&&t._immediateFn(function(){e._handled||t._unhandledRejectionFn(e._value)});for(var n=0,r=e._deferreds.length;r>n;n++)o(e,e._deferreds[n]);e._deferreds=null}function u(e,n){var t=!1;try{e(function(e){t||(t=!0,r(n,e))},function(e){t||(t=!0,i(n,e))})}catch(o){if(t)return;t=!0,i(n,o)}}var c=setTimeout;t.prototype["catch"]=function(e){return this.then(null,e)},t.prototype.then=function(e,t){var r=new this.constructor(n);return o(this,new function(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}(e,t,r)),r},t.prototype["finally"]=e,t.all=function(e){return new t(function(n,t){function o(e,f){try{if(f&&("object"==typeof f||"function"==typeof f)){var u=f.then;if("function"==typeof u)return void u.call(f,function(n){o(e,n)},t)}r[e]=f,0==--i&&n(r)}catch(c){t(c)}}if(!e||"undefined"==typeof e.length)throw new TypeError("Promise.all accepts an array");var r=Array.prototype.slice.call(e);if(0===r.length)return n([]);for(var i=r.length,f=0;r.length>f;f++)o(f,r[f])})},t.resolve=function(e){return e&&"object"==typeof e&&e.constructor===t?e:new t(function(n){n(e)})},t.reject=function(e){return new t(function(n,t){t(e)})},t.race=function(e){return new t(function(n,t){for(var o=0,r=e.length;r>o;o++)e[o].then(n,t)})},t._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){c(e,0)},t._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var l=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")}();"Promise"in l?l.Promise.prototype["finally"]||(l.Promise.prototype["finally"]=e):l.Promise=t}); -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reveal.js", 3 | "version": "3.8.0", 4 | "description": "The HTML Presentation Framework", 5 | "homepage": "http://revealjs.com", 6 | "subdomain": "revealjs", 7 | "main": "js/reveal.js", 8 | "scripts": { 9 | "test": "grunt test", 10 | "start": "grunt serve", 11 | "build": "grunt" 12 | }, 13 | "author": { 14 | "name": "Hakim El Hattab", 15 | "email": "hakim.elhattab@gmail.com", 16 | "web": "http://hakim.se" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/hakimel/reveal.js.git" 21 | }, 22 | "engines": { 23 | "node": ">=9.0.0" 24 | }, 25 | "devDependencies": { 26 | "express": "^4.16.2", 27 | "grunt": "^1.0.4", 28 | "grunt-cli": "^1.3.2", 29 | "grunt-autoprefixer": "^3.0.4", 30 | "grunt-contrib-connect": "^2.0.0", 31 | "grunt-contrib-cssmin": "^3.0.0", 32 | "grunt-contrib-jshint": "^2.0.0", 33 | "grunt-contrib-qunit": "^3.1.0", 34 | "grunt-contrib-uglify": "^3.3.0", 35 | "grunt-contrib-watch": "^1.1.0", 36 | "grunt-sass": "^3.0.2", 37 | "grunt-zip": "~0.17.1", 38 | "load-grunt-tasks": "^4.0.0", 39 | "node-sass": "^4.11.0", 40 | "mustache": "^2.3.0", 41 | "socket.io": "^2.2.0" 42 | }, 43 | "license": "MIT" 44 | } 45 | -------------------------------------------------------------------------------- /docs/plugin/elapsed-time-bar/elapsed-time-bar.js: -------------------------------------------------------------------------------- 1 | var ElapsedTimeBar = { 2 | // default value 3 | barColor: 'rgb(200,0,0)', 4 | pausedBarColor: 'rgba(200,0,0,.6)', 5 | 6 | isPaused: false, 7 | isFinished: false, 8 | 9 | allottedTime: null, 10 | timeProgressBar: null, 11 | startTime: null, 12 | pauseTime: null, 13 | pauseTimeDuration: 0, 14 | 15 | /** 16 | * initialize elements 17 | */ 18 | handleReady() { 19 | var config = Reveal.getConfig(); 20 | 21 | // activate this plugin if config.allottedTime exists. 22 | if (!config.allottedTime) { 23 | console.warn('Failed to start ElapsedTimeBar plugin. "allottedTime" property is required.'); 24 | return; 25 | } 26 | 27 | // set configurations 28 | this.barColor = config.barColor || this.barColor; 29 | this.pausedBarColor = config.pausedBarColor || this.pausedBarColor; 30 | 31 | // calc barHeight from config.barHeight or page-progress container 32 | var barHeight; 33 | var pageProgressContainer = document.querySelector('.progress'); 34 | if (config.progressBarHeight) { 35 | barHeight = parseInt(config.progressBarHeight, 10) + 'px'; 36 | 37 | // override height of page-progress container 38 | pageProgressContainer && (pageProgressContainer.style.height = barHeight); 39 | } else if (config.progress && pageProgressContainer) { 40 | // get height from page-progress container 41 | barHeight = pageProgressContainer.getBoundingClientRect().height + 'px'; 42 | } else { 43 | // default 44 | barHeight = '3px'; 45 | } 46 | 47 | // create container of time-progress 48 | var timeProgressContainer = document.createElement('div'); 49 | timeProgressContainer.classList.add('progress'); 50 | Object.entries({ 51 | display: 'block', 52 | position: 'fixed', 53 | bottom: config.progress ? barHeight : 0, 54 | width: '100%', 55 | height: barHeight 56 | }).forEach(([k, v]) => { 57 | timeProgressContainer.style[k] = v; 58 | }); 59 | document.querySelector('.reveal').appendChild(timeProgressContainer); 60 | 61 | // create content of time-progress 62 | this.timeProgressBar = document.createElement('div'); 63 | Object.entries({ 64 | height: '100%', 65 | willChange: 'width' 66 | }).forEach(([k, v]) => { 67 | this.timeProgressBar.style[k] = v; 68 | }); 69 | timeProgressContainer.appendChild(this.timeProgressBar); 70 | 71 | // start timer 72 | this.start(config.allottedTime); 73 | }, 74 | 75 | /** 76 | * update repeatedly using requestAnimationFrame. 77 | */ 78 | loop() { 79 | if (this.isPaused) return; 80 | var now = +new Date(); 81 | var elapsedTime = now - this.startTime - this.pauseTimeDuration; 82 | if (elapsedTime > this.allottedTime) { 83 | this.timeProgressBar.style.width = '100%'; 84 | this.isFinished = true; 85 | } else { 86 | this.timeProgressBar.style.width = elapsedTime / this.allottedTime * 100 + '%'; 87 | requestAnimationFrame(this.loop.bind(this)); 88 | } 89 | }, 90 | 91 | /** 92 | * set color of progress bar 93 | */ 94 | setBarColor() { 95 | if (this.isPaused) { 96 | this.timeProgressBar.style.backgroundColor = this.pausedBarColor; 97 | } else { 98 | this.timeProgressBar.style.backgroundColor = this.barColor; 99 | } 100 | }, 101 | 102 | /** 103 | * start(reset) timer with new allotted time. 104 | * @param {number} allottedTime 105 | * @param {number} [elapsedTime=0] 106 | */ 107 | start(allottedTime, elapsedTime = 0) { 108 | this.isFinished = false; 109 | this.isPaused = false; 110 | this.allottedTime = allottedTime; 111 | this.startTime = +new Date() - elapsedTime; 112 | this.pauseTimeDuration = 0; 113 | this.setBarColor(); 114 | this.loop(); 115 | }, 116 | 117 | reset() { 118 | this.start(this.allottedTime); 119 | }, 120 | 121 | pause() { 122 | if (this.isPaused) return; 123 | this.isPaused = true; 124 | this.pauseTime = +new Date(); 125 | this.setBarColor(); 126 | }, 127 | 128 | resume() { 129 | if (!this.isPaused) return; 130 | 131 | // add paused time duration 132 | this.isPaused = false; 133 | this.pauseTimeDuration += new Date() - this.pauseTime; 134 | this.pauseTime = null; 135 | this.setBarColor(); 136 | this.loop(); 137 | } 138 | }; 139 | 140 | if (Reveal.isReady()) { 141 | ElapsedTimeBar.handleReady(); 142 | } else { 143 | Reveal.addEventListener('ready', () => ElapsedTimeBar.handleReady()); 144 | } -------------------------------------------------------------------------------- /docs/plugin/markdown/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | reveal.js - Markdown Demo 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 |
23 | 24 | 25 |
26 | 36 |
37 | 38 | 39 |
40 | 54 |
55 | 56 | 57 |
58 | 69 |
70 | 71 | 72 |
73 | 77 |
78 | 79 | 80 |
81 | 86 |
87 | 88 | 89 |
90 | 100 |
101 | 102 | 103 |
104 | 107 |
108 | 109 |
110 |
111 | 112 | 113 | 114 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /docs/plugin/markdown/example.md: -------------------------------------------------------------------------------- 1 | # Markdown Demo 2 | 3 | 4 | 5 | ## External 1.1 6 | 7 | Content 1.1 8 | 9 | Note: This will only appear in the speaker notes window. 10 | 11 | 12 | ## External 1.2 13 | 14 | Content 1.2 15 | 16 | 17 | 18 | ## External 2 19 | 20 | Content 2.1 21 | 22 | 23 | 24 | ## External 3.1 25 | 26 | Content 3.1 27 | 28 | 29 | ## External 3.2 30 | 31 | Content 3.2 32 | 33 | 34 | ## External 3.3 35 | 36 | ![External Image](https://s3.amazonaws.com/static.slid.es/logo/v2/slides-symbol-512x512.png) 37 | -------------------------------------------------------------------------------- /docs/plugin/math/math.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A plugin which enables rendering of math equations inside 3 | * of reveal.js slides. Essentially a thin wrapper for MathJax. 4 | * 5 | * @author Hakim El Hattab 6 | */ 7 | var RevealMath = window.RevealMath || (function(){ 8 | 9 | var options = Reveal.getConfig().math || {}; 10 | var mathjax = options.mathjax || 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js'; 11 | var config = options.config || 'TeX-AMS_HTML-full'; 12 | var url = mathjax + '?config=' + config; 13 | 14 | var defaultOptions = { 15 | messageStyle: 'none', 16 | tex2jax: { 17 | inlineMath: [ [ '$', '$' ], [ '\\(', '\\)' ] ], 18 | skipTags: [ 'script', 'noscript', 'style', 'textarea', 'pre' ] 19 | }, 20 | skipStartupTypeset: true 21 | }; 22 | 23 | function defaults( options, defaultOptions ) { 24 | 25 | for ( var i in defaultOptions ) { 26 | if ( !options.hasOwnProperty( i ) ) { 27 | options[i] = defaultOptions[i]; 28 | } 29 | } 30 | 31 | } 32 | 33 | function loadScript( url, callback ) { 34 | 35 | var head = document.querySelector( 'head' ); 36 | var script = document.createElement( 'script' ); 37 | script.type = 'text/javascript'; 38 | script.src = url; 39 | 40 | // Wrapper for callback to make sure it only fires once 41 | var finish = function() { 42 | if( typeof callback === 'function' ) { 43 | callback.call(); 44 | callback = null; 45 | } 46 | } 47 | 48 | script.onload = finish; 49 | 50 | // IE 51 | script.onreadystatechange = function() { 52 | if ( this.readyState === 'loaded' ) { 53 | finish(); 54 | } 55 | } 56 | 57 | // Normal browsers 58 | head.appendChild( script ); 59 | 60 | } 61 | 62 | return { 63 | init: function() { 64 | 65 | defaults( options, defaultOptions ); 66 | defaults( options.tex2jax, defaultOptions.tex2jax ); 67 | options.mathjax = options.config = null; 68 | 69 | loadScript( url, function() { 70 | 71 | MathJax.Hub.Config( options ); 72 | 73 | // Typeset followed by an immediate reveal.js layout since 74 | // the typesetting process could affect slide height 75 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub ] ); 76 | MathJax.Hub.Queue( Reveal.layout ); 77 | 78 | // Reprocess equations in slides when they turn visible 79 | Reveal.addEventListener( 'slidechanged', function( event ) { 80 | 81 | MathJax.Hub.Queue( [ 'Typeset', MathJax.Hub, event.currentSlide ] ); 82 | 83 | } ); 84 | 85 | } ); 86 | 87 | } 88 | } 89 | 90 | })(); 91 | 92 | Reveal.registerPlugin( 'math', RevealMath ); 93 | -------------------------------------------------------------------------------- /docs/plugin/multiplex/client.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var multiplex = Reveal.getConfig().multiplex; 3 | var socketId = multiplex.id; 4 | var socket = io.connect(multiplex.url); 5 | 6 | socket.on(multiplex.id, function(data) { 7 | // ignore data from sockets that aren't ours 8 | if (data.socketId !== socketId) { return; } 9 | if( window.location.host === 'localhost:1947' ) return; 10 | 11 | Reveal.setState(data.state); 12 | }); 13 | }()); 14 | -------------------------------------------------------------------------------- /docs/plugin/multiplex/index.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var express = require('express'); 3 | var fs = require('fs'); 4 | var io = require('socket.io'); 5 | var crypto = require('crypto'); 6 | 7 | var app = express(); 8 | var staticDir = express.static; 9 | var server = http.createServer(app); 10 | 11 | io = io(server); 12 | 13 | var opts = { 14 | port: process.env.PORT || 1948, 15 | baseDir : __dirname + '/../../' 16 | }; 17 | 18 | io.on( 'connection', function( socket ) { 19 | socket.on('multiplex-statechanged', function(data) { 20 | if (typeof data.secret == 'undefined' || data.secret == null || data.secret === '') return; 21 | if (createHash(data.secret) === data.socketId) { 22 | data.secret = null; 23 | socket.broadcast.emit(data.socketId, data); 24 | }; 25 | }); 26 | }); 27 | 28 | [ 'css', 'js', 'plugin', 'lib' ].forEach(function(dir) { 29 | app.use('/' + dir, staticDir(opts.baseDir + dir)); 30 | }); 31 | 32 | app.get("/", function(req, res) { 33 | res.writeHead(200, {'Content-Type': 'text/html'}); 34 | 35 | var stream = fs.createReadStream(opts.baseDir + '/index.html'); 36 | stream.on('error', function( error ) { 37 | res.write('

reveal.js multiplex server.

Generate token'); 38 | res.end(); 39 | }); 40 | stream.on('readable', function() { 41 | stream.pipe(res); 42 | }); 43 | }); 44 | 45 | app.get("/token", function(req,res) { 46 | var ts = new Date().getTime(); 47 | var rand = Math.floor(Math.random()*9999999); 48 | var secret = ts.toString() + rand.toString(); 49 | res.send({secret: secret, socketId: createHash(secret)}); 50 | }); 51 | 52 | var createHash = function(secret) { 53 | var cipher = crypto.createCipher('blowfish', secret); 54 | return(cipher.final('hex')); 55 | }; 56 | 57 | // Actually listen 58 | server.listen( opts.port || null ); 59 | 60 | var brown = '\033[33m', 61 | green = '\033[32m', 62 | reset = '\033[0m'; 63 | 64 | console.log( brown + "reveal.js:" + reset + " Multiplex running on port " + green + opts.port + reset ); -------------------------------------------------------------------------------- /docs/plugin/multiplex/master.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // Don't emit events from inside of notes windows 4 | if ( window.location.search.match( /receiver/gi ) ) { return; } 5 | 6 | var multiplex = Reveal.getConfig().multiplex; 7 | 8 | var socket = io.connect( multiplex.url ); 9 | 10 | function post() { 11 | 12 | var messageData = { 13 | state: Reveal.getState(), 14 | secret: multiplex.secret, 15 | socketId: multiplex.id 16 | }; 17 | 18 | socket.emit( 'multiplex-statechanged', messageData ); 19 | 20 | }; 21 | 22 | // post once the page is loaded, so the client follows also on "open URL". 23 | window.addEventListener( 'load', post ); 24 | 25 | // Monitor events that trigger a change in state 26 | Reveal.addEventListener( 'slidechanged', post ); 27 | Reveal.addEventListener( 'fragmentshown', post ); 28 | Reveal.addEventListener( 'fragmenthidden', post ); 29 | Reveal.addEventListener( 'overviewhidden', post ); 30 | Reveal.addEventListener( 'overviewshown', post ); 31 | Reveal.addEventListener( 'paused', post ); 32 | Reveal.addEventListener( 'resumed', post ); 33 | 34 | }()); 35 | -------------------------------------------------------------------------------- /docs/plugin/multiplex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reveal-js-multiplex", 3 | "version": "1.0.0", 4 | "description": "reveal.js multiplex server", 5 | "homepage": "http://revealjs.com", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "engines": { 10 | "node": "~4.1.1" 11 | }, 12 | "dependencies": { 13 | "express": "~4.13.3", 14 | "grunt-cli": "~0.1.13", 15 | "mustache": "~2.2.1", 16 | "socket.io": "~1.3.7" 17 | }, 18 | "license": "MIT" 19 | } 20 | -------------------------------------------------------------------------------- /docs/plugin/notes-server/client.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | // don't emit events from inside the previews themselves 4 | if( window.location.search.match( /receiver/gi ) ) { return; } 5 | 6 | var socket = io.connect( window.location.origin ), 7 | socketId = Math.random().toString().slice( 2 ); 8 | 9 | console.log( 'View slide notes at ' + window.location.origin + '/notes/' + socketId ); 10 | 11 | window.open( window.location.origin + '/notes/' + socketId, 'notes-' + socketId ); 12 | 13 | /** 14 | * Posts the current slide data to the notes window 15 | */ 16 | function post() { 17 | 18 | var slideElement = Reveal.getCurrentSlide(), 19 | notesElement = slideElement.querySelector( 'aside.notes' ); 20 | 21 | var messageData = { 22 | notes: '', 23 | markdown: false, 24 | socketId: socketId, 25 | state: Reveal.getState() 26 | }; 27 | 28 | // Look for notes defined in a slide attribute 29 | if( slideElement.hasAttribute( 'data-notes' ) ) { 30 | messageData.notes = slideElement.getAttribute( 'data-notes' ); 31 | } 32 | 33 | // Look for notes defined in an aside element 34 | if( notesElement ) { 35 | messageData.notes = notesElement.innerHTML; 36 | messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string'; 37 | } 38 | 39 | socket.emit( 'statechanged', messageData ); 40 | 41 | } 42 | 43 | // When a new notes window connects, post our current state 44 | socket.on( 'new-subscriber', function( data ) { 45 | post(); 46 | } ); 47 | 48 | // When the state changes from inside of the speaker view 49 | socket.on( 'statechanged-speaker', function( data ) { 50 | Reveal.setState( data.state ); 51 | } ); 52 | 53 | // Monitor events that trigger a change in state 54 | Reveal.addEventListener( 'slidechanged', post ); 55 | Reveal.addEventListener( 'fragmentshown', post ); 56 | Reveal.addEventListener( 'fragmenthidden', post ); 57 | Reveal.addEventListener( 'overviewhidden', post ); 58 | Reveal.addEventListener( 'overviewshown', post ); 59 | Reveal.addEventListener( 'paused', post ); 60 | Reveal.addEventListener( 'resumed', post ); 61 | 62 | // Post the initial state 63 | post(); 64 | 65 | }()); 66 | -------------------------------------------------------------------------------- /docs/plugin/notes-server/index.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var express = require('express'); 3 | var fs = require('fs'); 4 | var io = require('socket.io'); 5 | var Mustache = require('mustache'); 6 | 7 | var app = express(); 8 | var staticDir = express.static; 9 | var server = http.createServer(app); 10 | 11 | io = io(server); 12 | 13 | var opts = { 14 | port : 1947, 15 | baseDir : __dirname + '/../../' 16 | }; 17 | 18 | io.on( 'connection', function( socket ) { 19 | 20 | socket.on( 'new-subscriber', function( data ) { 21 | socket.broadcast.emit( 'new-subscriber', data ); 22 | }); 23 | 24 | socket.on( 'statechanged', function( data ) { 25 | delete data.state.overview; 26 | socket.broadcast.emit( 'statechanged', data ); 27 | }); 28 | 29 | socket.on( 'statechanged-speaker', function( data ) { 30 | delete data.state.overview; 31 | socket.broadcast.emit( 'statechanged-speaker', data ); 32 | }); 33 | 34 | }); 35 | 36 | [ 'css', 'js', 'images', 'plugin', 'lib' ].forEach( function( dir ) { 37 | app.use( '/' + dir, staticDir( opts.baseDir + dir ) ); 38 | }); 39 | 40 | app.get('/', function( req, res ) { 41 | 42 | res.writeHead( 200, { 'Content-Type': 'text/html' } ); 43 | fs.createReadStream( opts.baseDir + '/index.html' ).pipe( res ); 44 | 45 | }); 46 | 47 | app.get( '/notes/:socketId', function( req, res ) { 48 | 49 | fs.readFile( opts.baseDir + 'plugin/notes-server/notes.html', function( err, data ) { 50 | res.send( Mustache.to_html( data.toString(), { 51 | socketId : req.params.socketId 52 | })); 53 | }); 54 | 55 | }); 56 | 57 | // Actually listen 58 | server.listen( opts.port || null ); 59 | 60 | var brown = '\033[33m', 61 | green = '\033[32m', 62 | reset = '\033[0m'; 63 | 64 | var slidesLocation = 'http://localhost' + ( opts.port ? ( ':' + opts.port ) : '' ); 65 | 66 | console.log( brown + 'reveal.js - Speaker Notes' + reset ); 67 | console.log( '1. Open the slides at ' + green + slidesLocation + reset ); 68 | console.log( '2. Click on the link in your JS console to go to the notes page' ); 69 | console.log( '3. Advance through your slides and your notes will advance automatically' ); 70 | -------------------------------------------------------------------------------- /docs/plugin/notes/notes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Handles opening of and synchronization with the reveal.js 3 | * notes window. 4 | * 5 | * Handshake process: 6 | * 1. This window posts 'connect' to notes window 7 | * - Includes URL of presentation to show 8 | * 2. Notes window responds with 'connected' when it is available 9 | * 3. This window proceeds to send the current presentation state 10 | * to the notes window 11 | */ 12 | var RevealNotes = (function() { 13 | 14 | var notesPopup = null; 15 | 16 | function openNotes( notesFilePath ) { 17 | 18 | if (notesPopup && !notesPopup.closed) { 19 | notesPopup.focus(); 20 | return; 21 | } 22 | 23 | if( !notesFilePath ) { 24 | var jsFileLocation = document.querySelector('script[src$="notes.js"]').src; // this js file path 25 | jsFileLocation = jsFileLocation.replace(/notes\.js(\?.*)?$/, ''); // the js folder path 26 | notesFilePath = jsFileLocation + 'notes.html'; 27 | } 28 | 29 | notesPopup = window.open( notesFilePath, 'reveal.js - Notes', 'width=1100,height=700' ); 30 | 31 | if( !notesPopup ) { 32 | alert( 'Speaker view popup failed to open. Please make sure popups are allowed and reopen the speaker view.' ); 33 | return; 34 | } 35 | 36 | /** 37 | * Connect to the notes window through a postmessage handshake. 38 | * Using postmessage enables us to work in situations where the 39 | * origins differ, such as a presentation being opened from the 40 | * file system. 41 | */ 42 | function connect() { 43 | // Keep trying to connect until we get a 'connected' message back 44 | var connectInterval = setInterval( function() { 45 | notesPopup.postMessage( JSON.stringify( { 46 | namespace: 'reveal-notes', 47 | type: 'connect', 48 | url: window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search, 49 | state: Reveal.getState() 50 | } ), '*' ); 51 | }, 500 ); 52 | 53 | window.addEventListener( 'message', function( event ) { 54 | var data = JSON.parse( event.data ); 55 | if( data && data.namespace === 'reveal-notes' && data.type === 'connected' ) { 56 | clearInterval( connectInterval ); 57 | onConnected(); 58 | } 59 | if( data && data.namespace === 'reveal-notes' && data.type === 'call' ) { 60 | callRevealApi( data.methodName, data.arguments, data.callId ); 61 | } 62 | } ); 63 | } 64 | 65 | /** 66 | * Calls the specified Reveal.js method with the provided argument 67 | * and then pushes the result to the notes frame. 68 | */ 69 | function callRevealApi( methodName, methodArguments, callId ) { 70 | 71 | var result = Reveal[methodName].apply( Reveal, methodArguments ); 72 | notesPopup.postMessage( JSON.stringify( { 73 | namespace: 'reveal-notes', 74 | type: 'return', 75 | result: result, 76 | callId: callId 77 | } ), '*' ); 78 | 79 | } 80 | 81 | /** 82 | * Posts the current slide data to the notes window 83 | */ 84 | function post( event ) { 85 | 86 | var slideElement = Reveal.getCurrentSlide(), 87 | notesElement = slideElement.querySelector( 'aside.notes' ), 88 | fragmentElement = slideElement.querySelector( '.current-fragment' ); 89 | 90 | var messageData = { 91 | namespace: 'reveal-notes', 92 | type: 'state', 93 | notes: '', 94 | markdown: false, 95 | whitespace: 'normal', 96 | state: Reveal.getState() 97 | }; 98 | 99 | // Look for notes defined in a slide attribute 100 | if( slideElement.hasAttribute( 'data-notes' ) ) { 101 | messageData.notes = slideElement.getAttribute( 'data-notes' ); 102 | messageData.whitespace = 'pre-wrap'; 103 | } 104 | 105 | // Look for notes defined in a fragment 106 | if( fragmentElement ) { 107 | var fragmentNotes = fragmentElement.querySelector( 'aside.notes' ); 108 | if( fragmentNotes ) { 109 | notesElement = fragmentNotes; 110 | } 111 | else if( fragmentElement.hasAttribute( 'data-notes' ) ) { 112 | messageData.notes = fragmentElement.getAttribute( 'data-notes' ); 113 | messageData.whitespace = 'pre-wrap'; 114 | 115 | // In case there are slide notes 116 | notesElement = null; 117 | } 118 | } 119 | 120 | // Look for notes defined in an aside element 121 | if( notesElement ) { 122 | messageData.notes = notesElement.innerHTML; 123 | messageData.markdown = typeof notesElement.getAttribute( 'data-markdown' ) === 'string'; 124 | } 125 | 126 | notesPopup.postMessage( JSON.stringify( messageData ), '*' ); 127 | 128 | } 129 | 130 | /** 131 | * Called once we have established a connection to the notes 132 | * window. 133 | */ 134 | function onConnected() { 135 | 136 | // Monitor events that trigger a change in state 137 | Reveal.addEventListener( 'slidechanged', post ); 138 | Reveal.addEventListener( 'fragmentshown', post ); 139 | Reveal.addEventListener( 'fragmenthidden', post ); 140 | Reveal.addEventListener( 'overviewhidden', post ); 141 | Reveal.addEventListener( 'overviewshown', post ); 142 | Reveal.addEventListener( 'paused', post ); 143 | Reveal.addEventListener( 'resumed', post ); 144 | 145 | // Post the initial state 146 | post(); 147 | 148 | } 149 | 150 | connect(); 151 | 152 | } 153 | 154 | return { 155 | init: function() { 156 | 157 | if( !/receiver/i.test( window.location.search ) ) { 158 | 159 | // If the there's a 'notes' query set, open directly 160 | if( window.location.search.match( /(\?|\&)notes/gi ) !== null ) { 161 | openNotes(); 162 | } 163 | 164 | // Open the notes when the 's' key is hit 165 | Reveal.addKeyBinding({keyCode: 83, key: 'S', description: 'Speaker notes view'}, function() { 166 | openNotes(); 167 | } ); 168 | 169 | } 170 | 171 | }, 172 | 173 | open: openNotes 174 | }; 175 | 176 | })(); 177 | 178 | Reveal.registerPlugin( 'notes', RevealNotes ); 179 | -------------------------------------------------------------------------------- /docs/plugin/print-pdf/print-pdf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * phantomjs script for printing presentations to PDF. 3 | * 4 | * Example: 5 | * phantomjs print-pdf.js "http://revealjs.com?print-pdf" reveal-demo.pdf 6 | * 7 | * @author Manuel Bieh (https://github.com/manuelbieh) 8 | * @author Hakim El Hattab (https://github.com/hakimel) 9 | * @author Manuel Riezebosch (https://github.com/riezebosch) 10 | */ 11 | 12 | // html2pdf.js 13 | var system = require( 'system' ); 14 | 15 | var probePage = new WebPage(); 16 | var printPage = new WebPage(); 17 | 18 | var inputFile = system.args[1] || 'index.html?print-pdf'; 19 | var outputFile = system.args[2] || 'slides.pdf'; 20 | 21 | if( outputFile.match( /\.pdf$/gi ) === null ) { 22 | outputFile += '.pdf'; 23 | } 24 | 25 | console.log( 'Export PDF: Reading reveal.js config [1/4]' ); 26 | 27 | probePage.open( inputFile, function( status ) { 28 | 29 | console.log( 'Export PDF: Preparing print layout [2/4]' ); 30 | 31 | var config = probePage.evaluate( function() { 32 | return Reveal.getConfig(); 33 | } ); 34 | 35 | if( config ) { 36 | 37 | printPage.paperSize = { 38 | width: Math.floor( config.width * ( 1 + config.margin ) ), 39 | height: Math.floor( config.height * ( 1 + config.margin ) ), 40 | border: 0 41 | }; 42 | 43 | printPage.open( inputFile, function( status ) { 44 | console.log( 'Export PDF: Preparing pdf [3/4]') 45 | printPage.evaluate( function() { 46 | Reveal.isReady() ? window.callPhantom() : Reveal.addEventListener( 'pdf-ready', window.callPhantom ); 47 | } ); 48 | } ); 49 | 50 | printPage.onCallback = function( data ) { 51 | // For some reason we need to "jump the queue" for syntax highlighting to work. 52 | // See: http://stackoverflow.com/a/3580132/129269 53 | setTimeout( function() { 54 | console.log( 'Export PDF: Writing file [4/4]' ); 55 | printPage.render( outputFile ); 56 | console.log( 'Export PDF: Finished successfully!' ); 57 | phantom.exit(); 58 | }, 0 ); 59 | }; 60 | } 61 | else { 62 | 63 | console.log( 'Export PDF: Unable to read reveal.js config. Make sure the input address points to a reveal.js page.' ); 64 | phantom.exit( 1 ); 65 | 66 | } 67 | } ); 68 | -------------------------------------------------------------------------------- /docs/plugin/tagcloud/tagcloud.js: -------------------------------------------------------------------------------- 1 | // tagcloud 2 | (function(){ 3 | [].forEach.call( document.querySelectorAll('[tagcloud]'), function(cloud) { 4 | // Find all tagcloud items with a weight defined and add them to this array 5 | var weights = [].slice.call(cloud.querySelectorAll('[tagcloud-weight]')) 6 | .map(function(el){ return el.getAttribute('tagcloud-weight') }) 7 | .sort(function(a, b){ return b-a }); // Sort descending 8 | 9 | var upperBound = weights[0]; 10 | var lowerBound = weights[ weights.length - 1 ]; 11 | var denominator = upperBound - lowerBound; 12 | var slideNotes = cloud.querySelectorAll('.notes'); 13 | var isBlackWhite = cloud.hasAttribute('bw'); 14 | 15 | /** 16 | * Parses the text, removing any notes and formats each node with a span if one 17 | * doesn't exist 18 | * 19 | * @param text {String} the text of the slide 20 | * @returns {String} the formatted slide content 21 | **/ 22 | function formatTags(text) { 23 | for(index = 0; index < slideNotes.length; ++index) { 24 | text = text.replace(slideNotes[index].textContent, ''); 25 | } 26 | //@see http://stackoverflow.com/questions/24512636/split-words-shuffle-jumble-letters 27 | var a = text.split(/\n/), 28 | n = a.length; 29 | 30 | if (cloud.hasAttribute('shuffle')) { 31 | //shuffle order 32 | for (var i = n - 1; i > 0; i--) { 33 | var j = Math.floor(Math.random() * (i + 1)); 34 | var tmp = a[i]; 35 | a[i] = a[j]; 36 | a[j] = tmp; 37 | } 38 | } //end if shuffle 39 | 40 | return a.filter(function(item) { 41 | return item.trim() !== ''; 42 | }) 43 | .map(function(item) { 44 | return ( item.indexOf('span') === -1 ) ? '' + item.trim() + ' ' : item.trim(); 45 | }) 46 | .join(""); 47 | 48 | } 49 | 50 | /** 51 | * Calculates the size of the element. 52 | * If one or more of the tags has a weight attribute, all sizes are based on weights. 53 | * If none of the elements have weights, the sizes are random. 54 | * 55 | * @param {DOM Element} the tag to calculate the size of. 56 | * @return {Number} the percentage to set the font size to 57 | **/ 58 | function calcSize(elem) { 59 | var prctnge; 60 | 61 | // At least one of our cloud items is weighted, base sizes around weights 62 | if( weights.length > 0 ) { 63 | var itemWeight = elem.getAttribute('tagcloud-weight') || 0; 64 | var numerator = itemWeight - lowerBound; 65 | prctnge = (numerator / denominator) * 150 + 50; 66 | } 67 | // None of the cloud items are weighted, base the size randomly 68 | else { 69 | prctnge = Math.random() * 150 + 50; 70 | } 71 | 72 | if (cloud.hasAttribute('large')) { 73 | prctnge = prctnge * 1.2; 74 | } 75 | 76 | return prctnge; 77 | } 78 | 79 | /** 80 | * Applies a color to the tag. 81 | * If one or more tags have a weight attribute, colors are more intense 82 | * based on the weight. Otherwise colors, are chosen randomly. 83 | * 84 | * @param {DOM Element} the tag to color. 85 | **/ 86 | function tagColor(elem, isBlackWhite) { 87 | var color; 88 | 89 | if (isBlackWhite) { 90 | var col = Math.round(Math.random() * 155 + 100); 91 | color = 'rgb('+ col +',' + col + ',' + col + ')'; 92 | } else { 93 | color = 'hsl('+ Math.random()*360 +', 40%, 50%)'; 94 | } 95 | 96 | return color; 97 | } 98 | 99 | // Replace the inner html of the slide with the formatted tags 100 | cloud.innerHTML = formatTags(cloud.innerHTML); 101 | 102 | // Append the slideNotes to the slide again 103 | for(index = 0; index < slideNotes.length; ++index) { 104 | cloud.appendChild(slideNotes[index]); 105 | } 106 | 107 | // Size and colour the cloud tags 108 | [].forEach.call( cloud.querySelectorAll('span'), function(elem) { 109 | elem.style.fontSize = calcSize(elem) + '%'; 110 | elem.classList.add('clouditem'); 111 | if( elem.hasAttribute('tagcloud-link') ) { 112 | newelem = document.createElement('a'); 113 | newelem.innerHTML = elem.innerHTML; 114 | newelem.style.color = tagColor(elem, isBlackWhite); 115 | newelem.setAttribute('href', '/#/' + elem.getAttribute('tagcloud-link')); 116 | elem.innerHTML = ''; 117 | elem.appendChild(newelem); 118 | } 119 | else { 120 | elem.style.color = tagColor(elem, isBlackWhite); 121 | } 122 | }); 123 | }); 124 | })(); -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andifalk/kubernetes-spring-io-2019/5051f326cca2e577a9670ef74063d9bb8fdd1b0f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | rootProject.name = 'kubernetes-spring-io-2019' 7 | 8 | include '01-config-and-secrets:hello-spring-kubernetes' 9 | include '01-config-and-secrets:hello-spring-cloud-kubernetes' 10 | include '02-discovery:question-client' 11 | include '02-discovery:answering-service' 12 | --------------------------------------------------------------------------------